diff --git a/.github/agents/obrona-expander.agent.md b/.github/agents/obrona-expander.agent.md index de06f95..cbbd060 100644 --- a/.github/agents/obrona-expander.agent.md +++ b/.github/agents/obrona-expander.agent.md @@ -181,6 +181,7 @@ Start with the most basic concepts, then build on them. Explain "graf" before "a - No fenced code blocks (4-space indented blocks only) - Polish language throughout (English in parentheses for standard terms) - Terms are ordered from foundational to advanced +- Images are NOT ASCII, images should be actual images, monchrome black and white laser printer a4 friendly ### BATCH PROCESSING diff --git a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md index 382b3a2..5cd83dc 100644 --- a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md +++ b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md @@ -533,6 +533,10 @@ Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny): Po V−1 iteracjach dist nadal maleje → V-ta iteracja: dist[src] + weight < dist[dst] → return None +![Bellman-Ford — ujemne wagi vs Dijkstra](img/bellman_ford_negative_weights.png) + +![Bellman-Ford — wykrywanie cyklu ujemnego](img/bellman_ford_negative_cycle.png) + ![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png) **A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu): @@ -3251,31 +3255,75 @@ Poniższe diagramy ilustrują kluczowe frameworki i modele omówione w pytaniu. ### Katalogowanie — trzy filary metodologii -**1. Ustandaryzowany szablon opisu** — każdy wzorzec opisany wg tego samego formatu: -- **Nazwa** — jedno słowo/fraza: „Layered", „Observer" -- **Problem/Kontekst** — kiedy stosować -- **Siły (forces)** — konkurencyjne wymagania do pogodzenia -- **Rozwiązanie** — struktura, diagram, zachowanie -- **Konsekwencje** — tradeoffs: co zyskujemy, co tracimy -- **Powiązane wzorce** — jakie wzorce współgrają lub konkurują -- **Znane zastosowania** — real-world examples +Pytanie „JAK są katalogowane?" = jaką METODĘ stosujemy, żeby z setek wzorców zrobić przeszukiwalny, porównywalny, kompozytowalny system wiedzy. Odpowiedź: trzy filary, razem tworzące kompletną metodologię. -**2. Klasyfikacja wieloosiowa** — wzorce organizowane wzdłuż kilku osi jednocześnie: +![Trzy filary katalogowania wzorców](img/q14_three_pillars.png) + +**1. Ustandaryzowany szablon opisu (pattern template)** — każdy wzorzec opisany wg tego samego formatu, dzięki czemu można je porównywać „pole po polu". Mnemonik: **NaPSiRoKo**. + +| Pole | Skrót | Co zawiera | Przykład (Observer, GoF) | +|------|-------|------------|--------------------------| +| **Nazwa** | **Na** | jedno słowo/fraza | Observer | +| **Problem** | **P** | kiedy stosować? | Obiekt zmienia stan → wielu zależnych musi zareagować, ale nie chcemy ich hard-codować | +| **Siły** | **Si** | konkurencyjne wymagania | loose coupling vs koszt powiadomień (100 obserwatorów = 100 wywołań) | +| **Rozwiązanie** | **Ro** | struktura + zachowanie | Subject trzyma listę Observer; przy zmianie woła notify() na każdym | +| **Konsekwencje** | **Ko** | tradeoffs +/− | (+) luźne wiązanie, (−) kaskada powiadomień, memory leaks jeśli nie odrejestrujemy | +| Powiązane | — | wzorce pokrewne | Mediator (centralizuje), Pub/Sub (rozproszony wariant) | +| Znane zastosowania | — | real-world | Java Swing listeners, C# events, React useState → re-render | + +![Wypełniona karta wzorca Observer](img/q14_observer_card_filled.png) + +**2. Klasyfikacja wieloosiowa** — wzorce organizowane wzdłuż kilku osi jednocześnie, jak książki w bibliotece (dział + półka + autor). + +Osie klasyfikacji: - **Skala**: architektoniczny (cały system) → projektowy (klasa) → idiomatyczny (linia kodu) - **Domena problemu**: kreacyjne / strukturalne / behawioralne (GoF) albo warstwy / komunikacja / dekompozycja (POSA) - **Atrybut jakościowy**: wydajność, skalowalność, testowalność, dostępność -**3. Język wzorców (pattern language)** — wzorce referują się wzajemnie, tworząc graf: -- Microservices → wymaga → API Gateway, Service Discovery, Circuit Breaker -- Observer → wariant architektoniczny → Event-Driven Architecture -- Nawigacja: „mam problem X → wzorzec A → prowadzi do problemu Y → wzorzec B" +Konkretny przykład — jak GoF klasyfikuje 23 wzorce na dwóch osiach: -**Konkretne katalogi:** -- **POSA** (1996) — wzorce architektoniczne: Layers, Pipes & Filters, Broker, MVC, Microkernel -- **GoF** (1994) — 23 wzorce projektowe: kreacyjne (5), strukturalne (7), behawioralne (11) -- **EIP** (2003) — wzorce integracji: Message Channel, Router, Aggregator -- **PoEAA** (2002) — enterprise: Repository, Unit of Work, Domain Model, Active Record -- **Cloud Patterns** (~2015) — chmurowe: Circuit Breaker, Sidecar, Saga, Strangler Fig +| | Kreacyjne (5) | Strukturalne (7) | Behawioralne (11) | +|---|---|---|---| +| **Klasa** | Factory Method | Adapter (class) | Interpreter, Template Method | +| **Obiekt** | Abstract Factory, Builder, Prototype, Singleton | Adapter (obj), Bridge, Composite, Decorator, Facade, Flyweight, Proxy | Chain of Resp., Command, Iterator, Mediator, Memento, **Observer**, State, Strategy, Visitor | + +Observer jest w komórce: **behawioralny × obiekt**. Wiedzieć GDZIE wzorzec leży = szybsze przypomnienie i porównanie z sąsiadami (Mediator, State, Strategy — też behawioralne obiektowe). + +![Mapa katalogów wzorców](img/q14_catalog_map.png) + +**3. Język wzorców (pattern language)** — wzorce referują się wzajemnie, tworząc nawigacyjny graf „zobacz też". Sens: masz problem → stosujesz wzorzec A → A rodzi nowy problem → wzorzec B go rozwiązuje. + +Konkretna nawigacja w praktyce: + + Problem: „monolith nie skaluje się" + ↓ + Wzorzec: Microservices + ↓ wymaga + Problem: „jak routować żądania do serwisów?" + ↓ + Wzorzec: API Gateway + ↓ rodzi problem + Problem: „co gdy serwis nie odpowiada?" + ↓ + Wzorzec: Circuit Breaker + ↓ rodzi problem + Problem: „jak zachować spójność transakcji?" + ↓ + Wzorzec: Saga + +Każdy wzorzec w katalogu ma pole „Powiązane wzorce" — to linki w tym grafie. + +![Nawigacja w języku wzorców](img/q14_pattern_language_navigation.png) + +**Konkretne katalogi** (5 głównych — mnemonik **PGEP+C** = „Paweł Grał Efektownie Pod Chmurami"): + +| Katalog | Rok | Autorzy | Skala | Domena | Przykładowe wzorce | +|---------|-----|---------|-------|--------|--------------------| +| **POSA** | 1996 | Buschmann et al. | architektoniczny | systemy | Layers, Pipes & Filters, Broker, MVC, Microkernel | +| **GoF** | 1994 | Gamma, Helm, Johnson, Vlissides | projektowy | obiekty | Factory, Singleton, Observer, Strategy (23 łącznie) | +| **EIP** | 2003 | Hohpe & Woolf | integracyjny | komunikacja między-systemowa | Message Channel, Router, Aggregator | +| **PoEAA** | 2002 | Martin Fowler | projektowy/arch. | enterprise | Repository, Unit of Work, Domain Model, Active Record | +| **Cloud** | ~2015 | Microsoft/AWS | architektoniczny | chmura | Circuit Breaker, Sidecar, Saga, Strangler Fig | ### Przykładowe wzorce @@ -3302,18 +3350,45 @@ Poniższe diagramy ilustrują kluczowe frameworki i modele omówione w pytaniu. ### Jak zapamiętać -- **Mnemonik katalogów „PGEP+C"**: **P**OSA → **G**oF → **E**IP → **P**oEAA + **C**loud - - Historia: „**P**aweł **G**rał **E**fektownie **P**od **C**hmurami" - - Chronologicznie: GoF '94 → POSA '96 → PoEAA '02 → EIP '03 → Cloud ~'15 -- **Szablon wzorca „NaPSiRoKo"**: **Na**zwa, **P**roblem, **Si**ły, **Ro**związanie, **Ko**nsekwencje - - Wyobraź sobie kartonowe pudełko: etykieta (Nazwa) → co nie działa (Problem) → wagi na szalce (Siły) → instrukcja montażu (Rozwiązanie) → lista „+" i „−" na boku (Konsekwencje) -- **3 filary katalogowania**: Szablon + Klasyfikacja + Język wzorców - - Analogia do encyklopedii: każde hasło ma ten sam format (szablon), jest w kategorii z innymi hasłami tego typu (klasyfikacja), i ma „zobacz też" (język wzorców) -- **„Monolith first"** — rozdzielaj gdy znasz granice domen -- **Wzorzec = Nazwa + Problem + Rozwiązanie + Konsekwencje** (minimum do zapamiętania z dowolnego katalogu) +**Mnemonik 1 — szablon wzorca „NaPSiRoKo":** +- **Na**zwa → **P**roblem → **Si**ły → **Ro**związanie → **Ko**nsekwencje +- Historyjka: „**Na**pisałem **P**roblem na kartce, **Si**ły mnie ciągnęły w dwie strony, **Ro**związałem go, a **Ko**nsekwencje spisałem na odwrocie" +- Wyobraź sobie kartonowe pudełko: etykieta (Nazwa) → co nie działa (Problem) → wagi na szalce (Siły) → instrukcja montażu (Rozwiązanie) → lista „+" i „−" na boku (Konsekwencje) + +**Mnemonik 2 — katalogi „PGEP+C" = „Paweł Grał Efektownie Pod Chmurami":** + + P = POSA (1996, systemy) „Paweł" + G = GoF (1994, obiekty) „Grał" + E = EIP (2003, integracja) „Efektownie" + P = PoEAA (2002, enterprise) „Pod" + C = Cloud (~2015, chmura) „Chmurami" + +- Chronologicznie: GoF '94 → POSA '96 → PoEAA '02 → EIP '03 → Cloud ~'15 +- Skala rośnie: GoF (obiekty) → PoEAA (aplikacja) → POSA/EIP (system) → Cloud (infrastruktura) + +**Mnemonik 3 — trzy filary katalogowania „SzKlaJ" = „Szklany Jar":** +- **Sz**ablon opisu (NaPSiRoKo) — każde hasło w tym samym formacie +- **Kla**syfikacja wieloosiowa — hasła posortowane w kategorie (jak dział w bibliotece) +- **J**ęzyk wzorców — hasła mają „zobacz też" (graf nawigacyjny) +- Analogia: encyklopedia. Każde hasło ma ten sam format (**Sz**ablon), jest w kategorii z innymi hasłami tego typu (**Kla**syfikacja), i ma „zobacz też" (**J**ęzyk wzorców) + +**Mnemonik 4 — GoF 3 kategorie „KSB" = „Kto Stworzył Budynek?":** +- **K**reacyjne (5) — JAK tworzyć obiekty? (Factory, Singleton, Builder, Prototype, Abstract Factory) +- **S**trukturalne (7) — JAK składać obiekty? (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy) +- **B**ehawioralne (11) — JAK obiekty komunikują? (Observer, Strategy, Command, State, Iterator...) +- Zapamiętaj liczby: 5 + 7 + 11 = 23 + +**Szybka ściąga — wzorzec na obronie:** +- Wzorzec = Nazwa + Problem + Rozwiązanie + Konsekwencje (minimum do zapamiętania z dowolnego katalogu) +- „Monolith first" — rozdzielaj gdy znasz granice domen - Katalogi wg skali: POSA = systemy, GoF = obiekty, EIP = komunikacja międzysystemowa -→ Diagramy do druku: `pytania/img/q14_pattern_template.png`, `pytania/img/q14_catalog_map.png` +→ Diagramy do druku: +- `pytania/img/q14_pattern_template.png` — szablon NaPSiRoKo +- `pytania/img/q14_catalog_map.png` — mapa katalogów PGEP+C +- `pytania/img/q14_three_pillars.png` — trzy filary katalogowania +- `pytania/img/q14_observer_card_filled.png` — wypełniona karta wzorca Observer +- `pytania/img/q14_pattern_language_navigation.png` — nawigacja w języku wzorców \newpage @@ -5283,6 +5358,42 @@ Paxos ma 3 role: **Proposer** (proponuje wartość), **Acceptor** (głosuje), ** --- +#### Pojęcia kluczowe dla progowania i Otsu + +**Wariancja (variance, σ²)** — miara tego, jak bardzo wartości RÓŻNIĄ SIĘ od swojej średniej. Im większa wariancja, tym bardziej „rozrzucone" są dane. Wzór: σ² = Σ(xᵢ - μ)² / n, gdzie μ to średnia. + + Przykład 1 — MAŁA wariancja (dane skupione): + wartości: [48, 50, 52, 49, 51] średnia μ = 50 + σ² = ((48-50)² + (50-50)² + (52-50)² + (49-50)² + (51-50)²) / 5 + = (4 + 0 + 4 + 1 + 1) / 5 = 2.0 + + Przykład 2 — DUŻA wariancja (dane rozrzucone): + wartości: [10, 90, 30, 80, 50] średnia μ = 52 + σ² = ((10-52)² + (90-52)² + (30-52)² + (80-52)² + (50-52)²) / 5 + = (1764 + 1444 + 484 + 784 + 4) / 5 = 896.0 + + Mała σ² = punkty blisko średniej = dane JEDNORODNE + Duża σ² = punkty daleko od średniej = dane RÓŻNORODNE + +**Wewnątrzklasowa (within-class)** — „wewnątrz klasy" oznacza, że mierzymy wariancję OSOBNO dla każdej grupy (klasy), a potem ważymy wynik proporcją pikseli w grupie. Jeśli klasa 0 ma piksele [30, 50, 45] a klasa 1 ma piksele [180, 200, 190], to σ²_wewnątrz = (udział_kl0 × σ²_kl0) + (udział_kl1 × σ²_kl1). + +**Wariancja wewnątrzklasowa (within-class variance)** — obliczasz wariancję KAŻDEJ klasy osobno, ważysz przez udział pikseli w tej klasie, sumujesz. Jeśli σ²_wewnątrz jest MAŁA → klasy są „jednorodne" (piksele w klasie 0 mają podobne jasności, piksele w klasie 1 też). + +**Co to znaczy „klasy jednorodne"?** — jednorodna klasa to taka, w której WSZYSTKIE piksele mają podobne wartości. Np. klasa „tło" ma jasności [195, 200, 198, 205] → jednorodna (σ² mała). Klasa mieszająca tło i obiekt [30, 200, 50, 190] → niejednorodna (σ² duża). Otsu szuka progu T, który daje NAJBARDZIEJ jednorodne klasy. + +**Histogram bimodalny (bimodal histogram)** — histogram z DWOMA wyraźnymi „garbami" (pikami). „Bi" = dwa, „modal" = moda (najczęstsza wartość). Typowy dla obrazów z jednym obiektem na tle — garb 1 odpowiada ciemnym pikselom (obiekt), garb 2 jasnym (tło). Otsu działa TYLKO gdy histogram jest bimodalny — bo szuka progu MIĘDZY garbami. + + Garb 1 (ciemne~60): piksele obiektu + Garb 2 (jasne~190): piksele tła + Dolina między garbami → tu Otsu stawia próg T! + + Gdyby histogram miał JEDEN garb (unimodalny) → brak naturalnego + podziału → Otsu wybierze losowy próg → słaby wynik. + +![Histogram bimodalny, wariancja wewnątrzklasowa i jednorodność klas — Otsu](img/q23_otsu_bimodal.png) + +--- + **Thresholding (progowanie)** — najprostsza metoda segmentacji. Pomysł: każdy piksel ma wartość jasności (0=czarny, 255=biały). Wybierz PRÓG T: piksel > T → klasa 1 (obiekt), piksel ≤ T → klasa 0 (tło). Działa lepiej niż się wydaje na prostych obrazach (tekst na kartce, RTG, dokumenty). Obraz (jasność pikseli): [50][200][180][30][220][190] @@ -5295,16 +5406,55 @@ Paxos ma 3 role: **Proposer** (proponuje wartość), **Acceptor** (głosuje), ** Problem: JAK wybrać T? Ręcznie → subiektywne. Rozwiązanie → Otsu. -**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). + Mnemonik: „PRÓG na bramce" — jak bramkarz, przepuszcza piksele jaśniejsze od T, + blokuje ciemniejsze. + +**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). Patrz diagram powyżej. + + Pseudokod Otsu: + best_T = 0 + min_var = ∞ + for T in 0..255: + c0 = piksele z jasność ≤ T + c1 = piksele z jasność > T + w0 = len(c0) / len(all_pixels) + w1 = len(c1) / len(all_pixels) + var = w0 * variance(c0) + w1 * variance(c1) + if var < min_var: + min_var = var + best_T = T + return best_T + + Mnemonik: „AUTO-bramkarz Otsu" — sam sprawdza 256 progów i wybiera najlepszy. + +--- + +#### Pojęcia kluczowe dla Region Growing **Region Growing (rozrastanie regionu)** — zaczynasz od jednego piksela „ziarna" (seed) wybranego ręcznie lub automatycznie. Sprawdzasz sąsiadów: jeśli sąsiad jest PODOBNY (np. |jasność_sąsiada - jasność_regionu| < próg), dodaj go do regionu. Powtarzaj aż nie ma więcej podobnych sąsiadów. Następnie nowy seed → nowy region. - Seed piksel (100,100) ma jasność 150 - Sąsiad (101,100) ma jasność 153 → |153-150|=3 < próg 10 → DODAJ - Sąsiad (100,101) ma jasność 200 → |200-150|=50 > próg 10 → ODRZUĆ (granica!) - Region rośnie jak „plama" od seeda +**Dlaczego seed „ręcznie LUB automatycznie"?** — to dwa różne scenariusze użycia: - Pseudokod: + RĘCZNY seed: + - Użytkownik klika myszką na obraz: „tu jest obiekt" + - Użycie: segmentacja interaktywna (Photoshop „magic wand", + narzędzia medyczne do zaznaczania guzów na RTG) + - Zaleta: precyzyjny, użytkownik wie co chce segmentować + - Wada: wymaga człowieka → nie skaluje się do 10 000 obrazów + + AUTOMATYCZNY seed — metody: + 1. Siatka (grid): seed co N pikseli (np. co 50 px na obrazie 500×500 → 100 seedów) + 2. Lokalne ekstrema histogramu: znajdź najczęstszą jasność → seed tam + 3. Losowanie: wylosuj K punktów jako seedy + 4. Analiza gradientu: piksele w „płaskich" regionach (brak krawędzi) → dobre seedy + + Dlaczego OR a nie AND? + Bo to ALTERNATYWNE podejścia — albo człowiek wybiera (mało i precyzyjnie), + albo algorytm wybiera (dużo i szybko, ale mniej precyzyjnie). + +![Region Growing: seed ręczny vs automatyczny, krok po kroku, fale BFS](img/q23_region_growing.png) + + Pseudokod Region Growing: region = {seed} queue = [seed] while queue not empty: @@ -5314,106 +5464,189 @@ Paxos ma 3 role: **Proposer** (proponuje wartość), **Acceptor** (głosuje), ** region.add(neighbor) queue.append(neighbor) - Problem: over-segmentation — drobne szumy → małe regiony - -**Watershed (metoda zlewiska)** — traktuje obraz jak mapę topograficzną: wartość jasności piksela = wysokość terenu. Ciemne piksele = doliny, jasne = szczyty. Algorytm „zalewa" mapę wodą od najniższych punktów (minimów). Gdy woda z dwóch dolin się spotyka — tam jest GRANICA segmentu (grań). - - Obraz jako mapa wysokości: - ████████ - ██ ██ ← jasne piksele = szczyty (granice) - █ dolina █ - █ (obiekt) █ - █ █ - ██ dolina ██ ← kolejna dolina (inny segment) - ████████ - - Algorytm: zalewamy od dołu → woda spotyka się na graniach → SEGMENTY - - Problem: MASYWNA over-segmentation — każde lokalne minimum (nawet szum) → osobna dolina - Rozwiązanie: marker-controlled watershed — ręcznie podaj „ziarna" (markers) - zamiast zalewać od KAŻDEGO minimum - -**Mean Shift** — iteracyjne przesuwanie okna (jądra) do punktu o najwyższej gęstości pikseli w przestrzeni cech. Cechy to np. (jasność, x, y) lub (R, G, B, x, y). Piksele, które zbiegają do tego samego maksimum gęstości, tworzą jeden segment. Wolny: O(n²), ale nie wymaga podania liczby segmentów. - - Wyobraź sobie rozsypane kulki na stole (= piksele w przestrzeni cech) - Każda kulka „toczy się" w kierunku najbliższej „góry kulek" (max gęstości) - Kulki, które dotoczyły się do tej samej góry → jeden segment - -**Normalized Cuts** — modeluje obraz jako graf: piksele = węzły, krawędzie łączą sąsiednie piksele z wagą = PODOBIEŃSTWO (im bardziej podobne, tym wyższa waga). Szukamy CIĘCIA grafu (podział na grupy) minimalizującego stosunek ciętych krawędzi do rozmiaru grup. „Znormalizowane" → unika tworzenia malutkich segmentów. O(n³) — bardzo kosztowny: obraz 100×100 = 10 000 węzłów → 10¹² operacji! + Mnemonik: „PLAMA atramentu" — seed to kropla atramentu na papierze, + rozlewa się na podobne (jasne) miejsca, zatrzymuje się na granicach. --- -**Sieć neuronowa (neural network)** — model uczenia maszynowego inspirowany biologicznymi neuronami. Składa się z warstw „neuronów" — każdy neuron oblicza ważoną sumę wejść + bias, przepuszcza przez funkcję aktywacji (np. ReLU = max(0,x)), i przekazuje wynik dalej. Sieć uczy się automatycznie z danych: dostaje pary (obraz, poprawna mapa segmentacji), dostosowuje wagi by minimalizować błąd. +#### Pojęcia kluczowe dla Watershed - Neuron: output = ReLU(w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + bias) - ReLU(x) = max(0, x) — prosta, ale bardzo skuteczna funkcja aktywacji - Uczenie: porównaj predykcję sieci z poprawną mapą (label) → oblicz błąd (loss) - → backpropagation → aktualizuj wagi → powtórz miliony razy +**Watershed (metoda zlewiska)** — traktuje obraz jak mapę topograficzną: wartość jasności piksela = wysokość terenu. Ciemne piksele = doliny, jasne = szczyty. Algorytm „zalewa" mapę wodą od najniższych punktów (minimów). Gdy woda z dwóch dolin się spotyka — tam jest GRANICA segmentu (grań). -**CNN (Convolutional Neural Network)** — sieć, której kluczowym elementem jest warstwa konwolucyjna (splotowa). Zamiast łączyć KAŻDY piksel z KAŻDYM neuronem (co byłoby niewykonalne — obraz 640×480 = 307 200 neuronów wejściowych!), CNN przesuwany mały filtr (np. 3×3 pikseli) po obrazie, obliczając w każdym miejscu iloczyn skalarny filtra z fragmentem obrazu. +![Watershed: obraz jako mapa topograficzna, zalewanie, over-segmentation i marker-controlled watershed](img/q23_watershed.png) - Co robi konwolucja? Filtr 3×3 „jedzie" po obrazie jak wycieraczka: + Algorytm: + 1. Zamień obraz na „mapę wysokości" (jasność = wysokość) + 2. Znajdź wszystkie lokalne minima (najciemniejsze punkty) + 3. „Zalewaj" od minimów — woda rośnie równomiernie + 4. Gdy woda z dwóch dolin się spotyka → postaw TAMĘ (granicę segmentu) + 5. Kontynuuj aż cały obraz zalany - Filtr (edge detector): Fragment obrazu: Wynik konwolucji: - [-1 0 1] [50 50 200] (-1·50 + 0·50 + 1·200 + - [-1 0 1] * [50 50 200] = -1·50 + 0·50 + 1·200 + - [-1 0 1] [50 50 200] -1·50 + 0·50 + 1·200) = 450 + Problem: MASYWNA over-segmentation — każde lokalne minimum (nawet szum!) → osobna dolina + Rozwiązanie: marker-controlled watershed — użytkownik podaje markery (seedy), + zalewamy TYLKO od tych markerów - Duża wartość → tu jest KRAWĘDŹ (przejście ciemne→jasne) + Mnemonik: „ZALEWANIE terenu" — wyobraź sobie model terenu z plasteliny w wannie. + Powoli nalewasz wodę → doliny się wypełniają → granie gór = granice segmentów. - Hierarchia cech w CNN (wyuczona automatycznie!): - Warstwa 1: krawędzie (|, —, /, \) - Warstwa 2: tekstury (paski, siatki, plamy) - Warstwa 3: części (koła, oczy, krawędź dachu) - Warstwa 4+: obiekty (twarz, samochód, drzewo) +--- -**Encoder-Decoder** — architektura segmentacji: encoder ZMNIEJSZA rozdzielczość obrazu (downsampling — pooling), wydobywając coraz bardziej abstrakcyjne cechy (krawędzie → tekstury → obiekty). Decoder ZWIĘKSZA rozdzielczość (upsampling — dekonwolucja lub interpolacja), odtwarzając mapę segmentacji o pełnej rozdzielczości. +#### Pojęcia kluczowe dla Mean Shift - Encoder (zmniejsza): [224×224] →pool→ [112×112] →pool→ [56×56] →pool→ [28×28] →pool→ [14×14] - Decoder (zwiększa): [14×14] →up→ [28×28] →up→ [56×56] →up→ [112×112] →up→ [224×224] +**Okno (window) / jądro (kernel)** — w kontekście Mean Shift to koło (lub kula w wielowymiarowej przestrzeni) o ustalonej szerokości (bandwidth = promień h) wokół aktualnego punktu. Wewnątrz okna algorytm oblicza „średnią ważoną" pozycji pikseli. Okno = jądro — to synonim. Nazwa „jądro" pochodzi od estymacji jądrowej gęstości (kernel density estimation, KDE). - Dlaczego nie sklasyfikować od razu KAŻDEGO piksela osobno? - Bo pojedynczy piksel nie ma kontekstu — nie wiesz, czy piksel o wartości 150 - to fragment nieba czy samochodu. Encoder-decoder widzi KONTEKST (cały obiekt) - i jednocześnie tworzy wynik o PEŁNEJ ROZDZIELCZOŚCI. + Okno o promieniu h = 30 wokół punktu (100, 150): + Bierze WSZYSTKIE piksele, których cechy (jasność, x, y) + są w odległości ≤ 30 od (100, 150). + Oblicza ich średnią → przesuwa okno NA TĘ ŚREDNIĄ. + Powtarza aż okno się „zatrzyma" (przesunięcie < ε). -**Skip connections (połączenia skrótowe)** — połączenia „na skróty" łączące warstwy encodera z odpowiadającymi warstwami decodera. Problem: encoder traci detale przestrzenne (GDZIE dokładnie jest krawędź) podczas poolingu. Skip connections PRZENOSZĄ te detale z encodera wprost do decodera, umożliwiając precyzyjne granice segmentów. +**Najwyższa gęstość (density peak)** — punkt w przestrzeni cech, gdzie jest NAJWIĘKSZE skupisko pikseli. Jak najwyższy szczyt góry w 3D. Mean Shift = „przesuń w kierunku średniej" → iteracyjnie zbliża się do szczytu gęstości. - Bez skip connections: decoder „wie" ŻE tu jest samochód, ale granice są rozmyte - Ze skip connections: decoder „wie" ŻE tu jest samochód AND DOKŁADNIE GDZIE jest krawędź +**Przestrzeń cech (feature space)** — każdy piksel jest opisany nie tylko pozycją (x, y) ale też cechami koloru (jasność, R, G, B). Przestrzeń cech to przestrzeń wielowymiarowa, np. (R, G, B, x, y) = 5 wymiarów. Piksele o podobnych kolorach i blisko siebie będą blisko w przestrzeni cech → tworzą klastry (skupiska). -**FCN (Fully Convolutional Network, 2015)** — pierwsza sieć w pełni konwolucyjna do segmentacji. Kluczowa innowacja: **zastąpienie warstw fully-connected** (FC → stały rozmiar wejścia) **konwolucjami** (→ dowolny rozmiar wejścia). Klasyczny CNN (np. VGG, AlexNet) kończy się warstwami FC, które wymagają stałego rozmiaru (np. 224×224). FCN zamienia FC na Conv 1×1, co pozwala przetwarzać obraz o DOWOLNYM rozmiarze i zwracać mapę segmentacji. + Piksel A: (x=100, y=200, R=30, G=25, B=35) → punkt w 5D + Piksel B: (x=102, y=201, R=32, G=27, B=33) → BLISKO A w 5D + Piksel C: (x=105, y=198, R=200, G=210, B=220) → DALEKO od A w 5D (inny kolor!) + → A i B w jednym segmencie, C w innym - Klasyczny CNN: Conv → Conv → Pool → ... → FC(4096) → FC(1000) → "kot" - FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa [H×W×C] - ↑ skip connections z encodera +**Dlaczego Mean Shift NIE wymaga podania liczby segmentów?** — W K-means musisz podać K=3 (trzy klastry) ZANIM uruchomisz algorytm. Mean Shift działa inaczej: każdy piksel startuje i „toczy się" do najbliższego szczytu gęstości. Ile jest szczytów = tyle segmentów. Algorytm sam ODKRYWA liczbę klastrów. Parametrem jest tylko bandwidth (szerokość okna h): duże h → mało szczytów → mało segmentów; małe h → dużo szczytów → dużo segmentów. -**U-Net (2015)** — encoder-decoder w kształcie litery „U" ze skip connections realizowanymi przez **concatenation** (złączenie) — cechy z encodera są DOKLEJANE do cech decodera w odpowiedniej warstwie. Zaprojektowany dla segmentacji medycznej, gdzie zbiory danych są MAŁE (np. 30 zdjęć RTG), więc U-Net intensywnie używa data augmentation (obroty, odbicia, elastyczne deformacje). +![Mean Shift: przestrzeń cech, jądro przesuwane do max gęstości, dlaczego bez K](img/q23_mean_shift.png) - Encoder ──skip (concat)──→ Decoder - ↓ ──skip (concat)──→ ↑ - ↓ ──skip (concat)──→ ↑ - bottleneck (najgłębsza warstwa) + Pseudokod Mean Shift: + for each pixel p: + x = p.features # np. (R, G, B, pos_x, pos_y) + repeat: + window = all pixels within distance h from x + x_new = weighted_mean(window) + if |x_new - x| < epsilon: + break + x = x_new + p.cluster = x # zbieżny punkt = ID klastra - Dlaczego „U"? Bo wizualnie encoder schodzi w dół (↓), bottleneck na dole, - decoder wraca do góry (↑) — tworząc kształt litery U. - Dlaczego concat a nie dodawanie? Więcej informacji — encoder features + decoder features - → sieć sama decyduje, które informacje wykorzystać. + Mnemonik: „KULKI toczą się do dołków" — rozsyp kulki na nierównym stole, + każda toczy się do najbliższego zagłębienia. Ile dołków = tyle segmentów. -**DeepLab v3+** — Google. Kluczowe innowacje: +--- -**Atrous (dilated) convolutions** — konwolucje z „dziurami" (fr. à trous = z dziurami). Standardowy filtr 3×3 patrzy na 3×3 = 9 sąsiednich pikseli. Atrous convolution z rate=2 patrzy na piksele z odstępem 2 — efektywnie widzi 5×5 obszar, ALE używa TYCH SAMYCH 9 parametrów (wag). Większe receptive field (pole widzenia) za darmo! +#### Pojęcia kluczowe dla Normalized Cuts - Zwykła konwolucja 3×3: [x][x][x] receptive field = 3×3 - Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5×5, 9 parametrów! - Dilated (rate=3): [x][ ][ ][x][ ][ ][x] receptive field = 7×7, 9 parametrów! +**Cięcie grafu (graph cut)** — graf to zbiór węzłów (pikseli) połączonych krawędziami (z wagami = podobieństwo). „Ciąć graf" to znaleźć LINIĘ dzielącą węzły na grupy, tak aby krawędzie „przecięte" tą linią miały niską wagę (= łączyły niepodobne piksele), a krawędzie wewnątrz grup miały wysoką wagę (= łączyły podobne piksele). - Dlaczego to ważne? Segmentacja wymaga KONTEKSTU — żeby wiedzieć, że piksel to - „droga", musisz zobaczyć otaczające budynki i niebo. Większe receptive field = więcej kontekstu. +**Jak szukamy cięcia?** — Naiwnie: sprawdź WSZYSTKIE możliwe podziały → wykładnicza złożoność. Normalized Cuts zamienia problem na rozwiązanie „problemu wartości własnych" (eigenvalue problem) macierzy Laplacianu grafu. Drugi najmniejszy wektor własny wskazuje, które piksele należą do grupy A (wartości dodatnie) a które do B (wartości ujemne). -**ASPP (Atrous Spatial Pyramid Pooling)** — równoległe zastosowanie atrous convolutions z WIELOMA rate (np. 6, 12, 18) + global average pooling, potem połączenie wyników. Każdy rate widzi kontekst w INNEJ skali → multi-scale features. +**Dlaczego „znormalizowane" (normalized)?** — Zwykłe cięcie (min-cut) ma wadę: preferuje odcinanie MALUTKICH grup (1 piksel odcięty = małe cięcie). Normalizowanie dzieli koszt cięcia przez rozmiar grup → duże, zrównoważone segmenty. -**Transformer-based (SegFormer, Mask2Former)** — najnowsze podejście zastępujące CNN transformerami. Kluczowy mechanizm: **self-attention** — każdy piksel „pyta" WSZYSTKIE inne piksele: „jak bardzo jesteś ze mną powiązany?" CNN widzi tylko lokalne okno (3×3, 5×5), a self-attention widzi CAŁY obraz naraz → lepsze rozumienie globalnych zależności (np. „ten piksel jest częścią tego samego samochodu co piksel 500 pikseli dalej"). Cena: O(n²) pamięci (n = liczba pikseli), ale jakość SOTA na benchmarkach. +![Normalized Cuts: obraz jako graf, cięcie, algorytm krok po kroku](img/q23_normalized_cuts.png) + + Pseudokod Normalized Cuts (uproszczony): + # 1. Zbuduj macierz podobieństwa W + for each pair of pixels (i, j): + W[i,j] = exp(-|color_i - color_j|^2 / sigma^2) # jeśli sąsiedzi + W[i,j] = 0 # jeśli odlegli + + # 2. Macierz stopni D + D = diag(sum(W, axis=1)) # D[i,i] = suma wiersza i + + # 3. Rozwiąż problem wartości własnych + (D - W) * y = lambda * D * y + # Weź DRUGI najm. wektor własny y (pierwszy = trywialny) + + # 4. Podziel piksele + segment_A = {i : y[i] > 0} + segment_B = {i : y[i] <= 0} + + Mnemonik: „CIĘCIE sznurków" — piksele połączone sznurkami (mocne = podobne). + Tnij SŁABE sznurki → dwie grupy. Normalizacja = nie odcinaj samotnych pikseli. + +--- + +#### Pojęcia kluczowe dla sieci neuronowych + +**ReLU (Rectified Linear Unit)** — najpopularniejsza funkcja aktywacji w sieciach neuronowych. Wzór: ReLU(x) = max(0, x). Jeśli wejście jest ujemne → wynik = 0 (neuron „milczy"). Jeśli wejście jest dodatnie → wynik = x (neuron „przepuszcza" sygnał bez zmiany). Prosta, ale bardzo skuteczna — szybsza od starszych funkcji (sigmoid, tanh), bo nie wymaga obliczania exp(). + + ReLU(-3) = max(0, -3) = 0 ← neuron „wyłączony" + ReLU(0) = max(0, 0) = 0 ← na granicy + ReLU(2.5) = max(0, 2.5) = 2.5 ← neuron „włączony", przekazuje 2.5 + + Dlaczego nie po prostu f(x) = x (bez progu)? + Bo liniowość → cała sieć = jedna warstwa liniowa (tracisz głębokość). + ReLU jest NIELINIOWA (ma „zakręt" w 0) → pozwala sieci uczyć się + skomplikowanych wzorców. + +![ReLU: wykres funkcji, dlaczego ReLU, przykład numeryczny](img/q23_relu.png) + +**Iloczyn skalarny (dot product)** — operacja na dwóch wektorach (listach liczb) dająca JEDNĄ liczbę. Mnożysz odpowiednie elementy parami i sumujesz wyniki. W CNN konwolucja = iloczyn skalarny filtra × fragment obrazu. Duży wynik = wektory „podobne" (filtr pasuje do fragmentu). + + a = [1, 3, -2] b = [4, -1, 5] + a · b = 1·4 + 3·(-1) + (-2)·5 = 4 - 3 - 10 = -9 + + W konwolucji: + filtr = [-1, 0, 1, -1, 0, 1, -1, 0, 1] (spłaszczony 3×3) + fragment = [50, 50, 200, 50, 50, 200, 50, 50, 200] + dot = (-1)·50 + 0·50 + 1·200 + ... = 450 → duży = krawędź! + +![Iloczyn skalarny: definicja, geometryczna interpretacja, użycie w konwolucji](img/q23_dot_product.png) + +--- + +**Warstwa Fully Connected (FC, gęsta, dense)** — warstwa, w której KAŻDY neuron jest połączony z KAŻDYM wejściem. Obraz 7×7×512 (po konwolucjach) = 25 088 wartości. FC z 4096 neuronami = 25 088 × 4 096 = **~103 miliony wag**. Wady: (1) wymaga STAŁEGO rozmiaru wejścia (zawsze 7×7×512), (2) traci informację GDZIE coś jest (spłaszcza przestrzeń na wektor 1D). + +**Konwolucja (convolution)** — operacja przesuwania małego filtra (np. 3×3) po obrazie. W każdej pozycji oblicza iloczyn skalarny filtra × fragment obrazu → jedną liczbę. TE SAME wagi filtra użyte w KAŻDEJ pozycji → dzielenie parametrów. Zachowuje informację przestrzenną (GDZIE coś jest). + +**Conv 1×1 (konwolucja punktowa)** — filtr o rozmiarze 1×1 pikseli. „Patrzy" na JEDEN piksel, ale WSZYSTKIE kanały (np. 512). Działa jak FC, ale OSOBNO dla KAŻDEGO piksela → zachowuje mapę H×W. FCN zamienia FC na Conv 1×1: zamiast spłaszczyć 7×7×512 → 25 088 → FC, robi Conv1×1 na KAŻDYM z 7×7 pikseli × 512 kanałów → mapa 7×7×C (C = liczba klas). + +**Jak FCN zamienia FC na Conv 1×1?** — Klasyczny CNN: ostatnia mapa cech 7×7×512 → FLATTEN → wektor 25 088 → FC → 1000 klas → „to jest kot". FCN: ostatnia mapa cech H×W×512 → Conv1×1(512→C) → mapa H×W×C → upsample do pełnej rozdzielczości. Kluczowa różnica: NIE spłaszczamy → możemy przetwarzać obraz o DOWOLNYM rozmiarze. + +**Skip connections z encodera** — w encoder-decoder encoder zmniejsza obraz (pooling): 224→112→56→28→14. W tym procesie traci DETALE przestrzenne (dokładne krawędzie). Skip connections = „drogi na skróty" — cechy z wczesnych warstw encodera (pełne detali) są przekazywane WPROST do odpowiednich warstw decodera. Decoder wie CO i GDZIE. + +![FCN: warstwa FC vs Conv 1×1, konwolucja, skip connections](img/q23_fc_vs_conv1x1.png) + +--- + +**U-Net — dlaczego kształt „U"?** — Narysuj architekturę: encoder zmniejsza rozdzielczość (bloki idą w DÓŁ po lewej stronie), bottleneck jest na dole, decoder zwiększa rozdzielczość (bloki idą W GÓRĘ po prawej stronie). Wizualnie tworzy literę „U". „Encoder schodzi w dół" = każda warstwa encodera ma MNIEJSZĄ rozdzielczość (224→112→56→28), wizualizowane jako bloki o malejącym rozmiarze ułożone jeden pod drugim. + +**Concatenation (konkatenacja, złączenie)** — operacja „sklejania" dwóch tensorów wzdłuż osi kanałów. Jeśli encoder na poziomie 2 daje mapę 128×128×64 kanałów, a decoder na poziomie 2 daje mapę 128×128×64 kanałów, to concatenation = 128×128×**128** kanałów (64+64). Różni się od DODAWANIA (addition), które daje 128×128×64 (element-wise sum). Concatenation zachowuje WIĘCEJ informacji — sieć sama wybiera, które kanały wykorzystać. + + Dodawanie (ResNet-style): + encoder [a, b, c] + decoder [x, y, z] = [a+x, b+y, c+z] → 3 kanały + + Concatenation (U-Net-style): + encoder [a, b, c] ++ decoder [x, y, z] = [a, b, c, x, y, z] → 6 kanałów! + → więcej informacji, sieć sama zdecyduje co ważne + +![U-Net: architektura w kształcie U, skip connections z concatenation, encoder ↓ decoder ↑](img/q23_unet_arch.png) + + Mnemonik U-Net: „Litera U — w dół i w górę" — encoder schodzi ↓ (zmniejsza), + decoder wraca ↑ (zwiększa), między nimi mosty (skip = concat). + +--- + +**Receptive field (pole widzenia, pole recepcyjne)** — ile pikseli WEJŚCIOWYCH wpływa na JEDEN piksel wyjściowy. Konwolucja 3×3 → RF = 3×3. Dwie konwolucje 3×3 pod rząd → RF = 5×5 (druga widzi 3×3 fragmenty, z których każdy widział 3×3 → efektywnie 5×5). Większe RF = neuron widzi większy kontekst = lepiej rozumie co to za piksel. + +**Dlaczego większe RF jest lepsze?** — Pojedynczy piksel o jasności 150 może być fragmentem nieba LUB samochodu. Patrząc na otoczenie 3×3 → nadal nie wiesz. Patrząc na otoczenie 50×50 → widzisz budynki obok → „to droga!". Segmentacja wymaga KONTEKSTU globalnego. + +**Rate (współczynnik dylatacji)** — parametr atrous (dilated) convolution. Rate=1 = zwykła konwolucja (filtr dotyka sąsiadów). Rate=2 = filtr próbkuje co DRUGI piksel → RF rośnie z 3×3 do 5×5 przy TYCH SAMYCH 9 wagach. Rate=3 → RF = 7×7. Większy kontekst za darmo (bez dodatkowych parametrów). + +**Global Average Pooling (GAP)** — operacja redukcji: mapa cech H×W×C → 1×1×C. Dla KAŻDEGO kanału oblicza ŚREDNIĄ ze wszystkich H×W pikseli. Wynik: jeden wektor o wymiarze C, reprezentujący „średnią informację" z całego obrazu. RF = nieskończone (cały obraz). Używane w ASPP DeepLab jako jedna z równoległych gałęzi. + + Mapa cech 7×7×512: + Kanał 0: macierz 7×7 wartości → średnia → jedna liczba + Kanał 1: macierz 7×7 wartości → średnia → jedna liczba + ... + Kanał 511: macierz 7×7 wartości → średnia → jedna liczba + Wynik: wektor [avg₀, avg₁, ..., avg₅₁₁] → 1×1×512 + +![Receptive field: zwykła vs dilated konwolucja, rate, global average pooling](img/q23_receptive_field.png) + +--- + +**Transformer** — architektura sieci neuronowej zaproponowana w 2017 (Vaswani et al., „Attention Is All You Need"). Oryginalnie dla NLP (tłumaczenie), od 2020 (ViT — Vision Transformer) stosowana w wizji komputerowej. Kluczowy mechanizm: **self-attention** — każdy element (piksel/token) „pyta" WSZYSTKIE inne elementy: „jak bardzo jesteś ze mną powiązany?". Każdy element tworzy trzy wektory: Q (Query — czego szukam?), K (Key — co oferuję), V (Value — moja wartość). Attention = softmax(Q·Kᵀ / √d) · V. Koszt: O(n²) pamięci (n = liczba elementów). + +**SOTA (State Of The Art)** — najlepszy znany wynik na danym benchmarku (zbiorze testowym) w danym momencie. Np. „Mask2Former osiąga mIoU 57.8% na ADE20K — to aktualny SOTA". SOTA ciągle się zmienia — każdy nowy paper może pobić poprzedni rekord. + +![Transformer: CNN lokalny vs Transformer globalny, self-attention Q/K/V, SOTA](img/q23_transformer_attention.png) --- @@ -5426,8 +5659,6 @@ Paxos ma 3 role: **Proposer** (proponuje wartość), **Acceptor** (głosuje), ** **Focal Loss** — modyfikacja cross-entropy redukująca wpływ łatwych przykładów, skupiająca uczenie na trudnych. Kluczowa przy class imbalance (np. 99% tła, 1% obiekt). -**Receptive field** — ile wejścia „widzi" jeden neuron. Większe receptive field = kontekst globalny. Atrous convolutions zwiększają receptive field bez zwiększania parametrów. - --- ### Problem: czym jest segmentacja obrazu? @@ -5466,28 +5697,36 @@ Segmentacja obrazu to **przypisanie etykiety klasy KAŻDEMU pikselowi** obrazu. Metody niewymagające uczenia maszynowego — oparte na ręcznie zdefiniowanych regułach (próg, podobieństwo, struktura grafu). -| Metoda | Idea | Wada | Złożoność | -|--------|------|------|-----------| -| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) | -| **Otsu** | automatyczny próg (min wariancja wewnątrzklasowa) | j.w. ale dobiera T sam | O(n·L) | -| **Region Growing** | dodawaj sąsiednie piksele o podobnej wartości | over-segmentation, zależy od seeda | O(n) | -| **Watershed** | obraz = mapa wysokości, granice = granie gór | over-segmentation | O(n log n) | -| **Mean Shift** | iteracyjnie przesuwaj jądro do max gęstości | wolny | O(n²) | -| **Normalized Cuts** | piksele = węzły grafu, minimalizuj znormalizowane cięcie | bardzo wolny | O(n³) | +| Metoda | Idea | Wada | Złożoność | Mnemonik | +|--------|------|------|-----------|----------| +| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) | „PRÓG na bramce" | +| **Otsu** | automatyczny próg (min wariancja wewnątrzklasowa) | j.w. ale dobiera T sam | O(n·L) | „AUTO-bramkarz" | +| **Region Growing** | dodawaj sąsiednie piksele o podobnej wartości | over-segmentation, zależy od seeda | O(n) | „PLAMA atramentu" | +| **Watershed** | obraz = mapa wysokości, granice = granie gór | over-segmentation | O(n log n) | „ZALEWANIE terenu" | +| **Mean Shift** | iteracyjnie przesuwaj jądro do max gęstości | wolny | O(n²) | „KULKI toczą się" | +| **Normalized Cuts** | piksele = węzły grafu, minimalizuj znormalizowane cięcie | bardzo wolny | O(n³) | „CIĘCIE sznurków" | -**Przykład — Thresholding (Otsu):** +#### DIY Przykład — Thresholding (Otsu) krok po kroku - Obraz grayscale: [30][200][180][45][210][190] - Otsu automatycznie dobiera próg T=128: - Wynik: [ 0 ][ 1 ][ 1 ][ 0][ 1 ][ 1 ] - Zastosowanie: oddzielenie tekstu od tła (OCR), analiza zdjęć RTG +Poniższy diagram pokazuje CAŁY pipeline progowania Otsu od obrazu wejściowego do wyniku. Obraz syntetyczny 64×64 z ciemnym kołem na jasnym tle — typowy przypadek bimodalny. -**Przykład — Watershed:** +![DIY Thresholding + Otsu: obraz → histogram bimodalny → progowanie → szukanie min σ² → pseudokod → wynik](img/q23_diy_thresholding.png) - Obraz traktowany jako mapa topograficzna: - Jasne piksele = szczyty, ciemne = doliny - "Zalewamy" od minimów → woda spotyka się na graniach → GRANICE segmentów - Problem: za wiele minimów → over-segmentation → potrzeba markers + Pseudokod Otsu (Python-style): + best_T, min_var = 0, float('inf') + for T in range(256): + c0 = pixels[pixels <= T] # piksele ciemne + c1 = pixels[pixels > T] # piksele jasne + if len(c0) == 0 or len(c1) == 0: + continue + w0 = len(c0) / len(pixels) # udział klasy 0 + w1 = len(c1) / len(pixels) # udział klasy 1 + var = w0 * variance(c0) + w1 * variance(c1) # σ² wewnątrzklasowa + if var < min_var: + min_var = var + best_T = T + # best_T = optymalny próg (np. 128) + result = (pixels > best_T).astype(int) # binaryzacja **Wspólna wada klasycznych metod:** wymagają ręcznego doboru parametrów (próg, seed, kernel), nie uczą się cech z danych, słabe na złożonych obrazach naturalnych. @@ -5503,23 +5742,26 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy Decoder: cechy [14] → [28] → [56] → [112] → [224×224] (odtwarza MAPĘ) bottleneck -| Sieć | Rok | Kluczowa innowacja | Use case | -|------|-----|-------------------|----------| -| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end | -| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | -| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | -| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | -| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | +| Sieć | Rok | Kluczowa innowacja | Use case | Mnemonik | +|------|-----|-------------------|----------|----------| +| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end | „FC → Conv 1×1" | +| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | „Litera U + mosty" | +| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | „DZIURY w filtrze" | +| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | „WSZYSCY ze WSZYSTKIMI" | +| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | „WSZYSCY ze WSZYSTKIMI" | **FCN (Fully Convolutional Network):** + Mnemonik: „FC → Conv 1×1 = otwieramy bramkę dla DOWOLNEGO rozmiaru" Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot" - FCN: Conv → Conv → Pool → ... → Conv → Upsample → mapa pikseli - Innowacja: zamiana FC na Conv → wejście dowolnego rozmiaru + FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa pikseli + Innowacja: zamiana FC na Conv1×1 → wejście dowolnego rozmiaru Skip connections: łączą cechy z encodera → zachowują detale przestrzenne **U-Net:** + Mnemonik: „Litera U + mosty" — schodzisz w dół, wracasz w górę, + po drodze mosty (skip connections z concat) przenoszą detale. Encoder (↓) Decoder (↑) [64]────skip────→[64] ← skip connections = concatenation [128]───skip───→[128] (przenosi detale z encodera do decodera) @@ -5530,6 +5772,8 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **DeepLab v3+:** + Mnemonik: „DZIURY w filtrze" — filtr dosłownie ma dziury (à trous), + przez co widzi dalej bez dodatkowych parametrów. Zwykła konwolucja 3×3: [x][x][x] receptive field = 3 Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5, te same parametry! ASPP: równolegle rate=6,12,18 → multi-scale features → łączenie @@ -5537,10 +5781,34 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **Transformery (SegFormer, Mask2Former):** + Mnemonik: „WSZYSCY ze WSZYSTKIMI" — każdy piksel rozmawia z KAŻDYM innym. CNN: filtr 3×3 widzi LOKALNY kontekst (sąsiadów) Transformer: self-attention widzi CAŁY obraz naraz Cena: O(n²) pamięci (n = piksele), ale lepsze wyniki +#### DIY Przykład — U-Net krok po kroku + +Poniższy diagram pokazuje CAŁY pipeline U-Net od obrazu wejściowego do mapy segmentacji. Obraz syntetyczny 64×64 z dwoma obiektami (koła) na jasnym tle. + +![DIY U-Net: obraz → encoder zmniejsza → bottleneck → decoder zwiększa + skip → mapa segmentacji → pseudokod](img/q23_diy_unet.png) + + Pseudokod U-Net (PyTorch-style): + # ENCODER — zmniejsza rozdzielczość, wyciąga cechy + e1 = conv_block(input, filters=64) # [64×64×64] + e2 = conv_block(maxpool(e1), filters=128) # [32×32×128] + e3 = conv_block(maxpool(e2), filters=256) # [16×16×256] + + # BOTTLENECK — najgłębsza warstwa + b = conv_block(maxpool(e3), filters=512) # [8×8×512] + + # DECODER — zwiększa rozdzielczość + skip connections (concat!) + d3 = conv_block(concat(upconv(b), e3), filters=256) # [16×16×256] + d2 = conv_block(concat(upconv(d3), e2), filters=128) # [32×32×128] + d1 = conv_block(concat(upconv(d2), e1), filters=64) # [64×64×64] + + # WYNIK — Conv 1×1 → mapa klas + output = conv_1x1(d1, n_classes=3) # [64×64×3] → argmax → [64×64] etykiety + --- ### Metryki i funkcje kosztu @@ -5558,9 +5826,40 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy ### Jak zapamiętać -- **U-Net = „U-shape + skip connections"** — encoder-decoder -- **DeepLab = „Atrous (dilated) convolutions + ASPP"** -- **mIoU = Intersection / Union, uśrednione per klasa** +**Super-mnemonik na kolejność algorytmów:** + + „Turyści Oglądają Rzekę, Wodospad, Morze, Nurt — Fotografują Uroczy Dwór Tajemnic" + + Klasyczne: Thresholding → Otsu → Region growing → Watershed → Mean shift → Normalized cuts + Neuronowe: FCN → U-Net → DeepLab → Transformer + +![Mnemoniki: karty z algorytmami segmentacji i ich skojarzeniami](img/q23_mnemonics.png) + +**Mnemoniki per algorytm — STRATEGIE KLASYCZNE:** + +| Algorytm | Mnemonik | Skojarzenie | +|----------|----------|-------------| +| **Thresholding** | „PRÓG na bramce" | Bramkarz przepuszcza piksele > T, blokuje ≤ T | +| **Otsu** | „AUTO-bramkarz" | Sam sprawdza 256 progów, wybiera najlepszy (min σ²) | +| **Region Growing** | „PLAMA atramentu" | Kropla atramentu rozlewa się na podobne piksele (BFS) | +| **Watershed** | „ZALEWANIE terenu" | Woda zalewa doliny, granie gór = granice segmentów | +| **Mean Shift** | „KULKI toczą się do dołków" | Każda kulka → max gęstości, ile dołków = tyle segmentów | +| **Normalized Cuts** | „CIĘCIE sznurków" | Tnij słabe sznurki (krawędzie grafu), zachowaj silne | + +**Mnemoniki per algorytm — SIECI NEURONOWE:** + +| Sieć | Mnemonik | Skojarzenie | +|------|----------|-------------| +| **FCN** | „FC → Conv 1×1" | Otwiera bramkę dla dowolnego rozmiaru wejścia | +| **U-Net** | „Litera U + mosty" | Schodzisz ↓, wracasz ↑, mosty (skip concat) przenoszą detale | +| **DeepLab** | „DZIURY w filtrze" | Filtr ma dziury (à trous) → widzi dalej bez dodatkowych wag | +| **Transformer** | „WSZYSCY ze WSZYSTKIMI" | Każdy piksel pyta każdy inny (self-attention, O(n²)) | + +**Mnemoniki per metrykę:** + +- **mIoU** = „Nakładka / Suma" → intersection / union, uśrednione per klasa +- **Dice** = „Dwie nakładki / Razem" → 2·|A∩B| / (|A|+|B|) +- **Focal** = „Fokus na TRUDNYCH" → trudne piksele ważą więcej \newpage @@ -5580,11 +5879,198 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **Bounding box (prostokąt ograniczający, bbox)** — prostokąt opisujący położenie obiektu. Zwykle: (x_min, y_min, x_max, y_max) lub (x_center, y_center, width, height). Przybliżenie — obiekty rzadko są prostokątne. -**Confidence (pewność)** — wynik 0-1 mówiący jak pewny jest detektor, że wykrył obiekt danej klasy. Zwykle próg np. 0.5: detiekcje poniżej odrzucane. +**Confidence (pewność)** — wynik 0-1 mówiący jak pewny jest detektor, że wykrył obiekt danej klasy. Zwykle próg np. 0.5: detekcje poniżej odrzucane. --- -**Klasyfikator (classifier)** — model przypisujący etykietę do wejścia. Np. CNN trenowany na ImageNet: obraz → „kot" (+ prawdopodobieństwo). SAM nie lokalizuje — mówi tylko co jest na obrazie. Pytanie brzmi: jak z takiego modelu zbudować detektor? +**CNN (Convolutional Neural Network, konwolucyjna sieć neuronowa)** — typ sieci neuronowej zaprojektowany specjalnie do przetwarzania OBRAZÓW. Używany w KAŻDYM nowoczesnym detektorze (R-CNN, YOLO, SSD, DETR). Kluczowa idea: zamiast łączyć KAŻDY piksel z KAŻDYM neuronem (→ miliardy parametrów), CNN używa MAŁYCH filtrów (np. 3×3 piksele) przesuwanych po obrazie. Dzięki temu: +1. Mało parametrów (filtr 3×3 = 9 wag, niezależnie od rozmiaru obrazu) +2. Wykrywa lokalne wzorce (krawędzie, rogi, tekstury) +3. Inwariantność na przesunięcie (kot w lewym rogu = kot w prawym rogu) + + Dlaczego CNN a nie zwykła sieć neuronowa? + Obraz 224×224×3 = 150 528 pikseli. + Zwykła sieć (FC): 150 528 × 4096 neuronów = 616 MILIONÓW wag w 1 warstwie! + CNN: filtr 3×3×3 = 27 wag, przesuwany po CAŁYM obrazie → 27 wag zamiast 616M! + + Mnemonik: CNN = „Czytaj Nie Naraz" — nie bierzesz całego obrazu naraz, + tylko małe fragmenty (filtry 3×3), krok po kroku. + +**Konwolucja (convolution)** — podstawowa operacja CNN: mały filtr (macierz np. 3×3) przesuwa się po obrazie, w każdej pozycji mnoży element-po-elemencie z fragmentem obrazu i sumuje → jedna liczba na wyjściu. Wynik = „feature mapa" — mapa pokazująca GDZIE na obrazie dany wzorzec jest obecny. + + Przykład liczbowy: + Fragment obrazu 3×3: Filtr 3×3: Wynik (1 piksel feature mapy): + [1 2 3] [-1 0 1] + [4 5 6] × [-1 0 1] = 1(-1)+2(0)+3(1)+4(-1)+5(0)+6(1)+7(-1)+8(0)+9(1) + [7 8 9] [-1 0 1] = (-1+0+3) + (-4+0+6) + (-7+0+9) = 6 + + Ten filtr wykrywa PIONOWE KRAWĘDZIE (liczy różnicę prawa-lewa strona). + Duży wynik (6) = silna krawędź. Wynik ≈ 0 = brak krawędzi. + Filtr przesuwa się po CAŁYM obrazie → cała mapa cech. + + Pseudokod konwolucji: + def convolve(image, filter_3x3): + output = zeros(image.height - 2, image.width - 2) + for y in range(1, image.height - 1): + for x in range(1, image.width - 1): + patch = image[y-1:y+2, x-1:x+2] # wycinek 3×3 + output[y-1][x-1] = sum(patch * filter) # iloczyn + suma + return output + +**Filtr / Kernel** — mała macierz wag (np. 3×3, 5×5) uczona AUTOMATYCZNIE podczas treningu. CNN ma WIELE filtrów — każdy uczy się wykrywać INNY wzorzec. 64 filtry w jednej warstwie → 64 map cech. + + KLUCZOWA RÓŻNICA: w HOG cechy projektuje CZŁOWIEK. + W CNN filtry uczy się SIEĆ SAMA — to główna przewaga deep learning! + + Warstwa conv z 64 filtrami 3×3: + Filtr 1: nauczył się wykrywać pionowe krawędzie + Filtr 2: nauczył się wykrywać poziome krawędzie + Filtr 3: nauczył się wykrywać rogi + ... + Filtr 64: jakiś inny wzorzec pomocny w rozpoznawaniu + +**Feature map (mapa cech)** — wynik zastosowania JEDNEGO filtra do obrazu. Jasne piksele = „tu jest ten wzorzec". 64 filtry → 64 map cech → tensor [H × W × 64]. Feature mapy to WEWNĘTRZNA REPREZENTACJA tego, co sieć „widzi" na obrazie. + + Hierarchia cech w CNN (każda warstwa coraz bardziej abstrakcyjna): + Warstwa 1: krawędzie, gradienty (jak HOG!) + Warstwa 2: rogi, proste tekstury + Warstwa 3: fragmenty obiektów (oko, koło, ucho) + Warstwa 4+: całe obiekty (twarz = oczy+nos+usta, samochód = koła+okna+dach) + + Mnemonik: „K-R-F-O" = „Każdy Rycerz Znajduje Obiekt" + (Krawędzie → Rogi → Fragmenty → Obiekty) + +**Pooling (łączenie / podpróbkowanie)** — warstwa ZMNIEJSZAJĄCA rozmiar feature mapy. Najczęstsza: **max pooling 2×2** — z każdego bloku 2×2 pikseli zachowaj MAKSIMUM. Wynik: mapa 2× mniejsza w każdym wymiarze (= 4× mniej pikseli), ale zachowuje najsilniejsze cechy. + + Feature map 4×4: Po Max Pool 2×2: + [1 3 | 2 1] [3 2] ← max(1,3,0,3)=3 max(2,1,1,2)=2 + [0 3 | 1 2] [4 3] ← max(0,4,1,2)=4 max(1,0,3,1)=3 + ───────────── + [0 4 | 1 0] Rozmiar: 4×4 → 2×2 (4× mniej danych!) + [1 2 | 3 1] Zachowane: najsilniejsze cechy z każdego bloku + + Dlaczego max pooling? + 1. Mniej pikseli = mniej obliczeń w następnych warstwach + 2. Większe „pole widzenia" (receptive field) — warstwa „widzi" większy fragment + 3. Odporność na małe przesunięcia: obiekt ±1px → ten sam max + +**Stride (krok)** — o ile pikseli filtr przesuwa się za jednym krokiem. Stride=1: co 1 piksel (wyjście duże). Stride=2: co 2 piksele (wyjście 2× mniejsze). Max pool 2×2 ze stride 2 = typowy pooling. + +**FC (Fully Connected layer, warstwa w pełni połączona)** — warstwa, w której KAŻDY neuron jest połączony z KAŻDYM wyjściem poprzedniej warstwy. W CNN zwykle na KOŃCU sieci: feature mapy (3D) → spłaszczone do wektora 1D → FC klasyfikuje. + + CNN: Conv → Pool → Conv → Pool → [Flatten] → FC(4096) → FC(1000) → "kot" + ↑ ↑ + spłaszcz 3D→1D 1000 klas (ImageNet) + + FC = „warstwa decyzyjna" — łączy cechy z CAŁEGO obrazu w jedną decyzję. + Mnemonik: FC = „Full Connection" — każdy z każdym, jak klasa każdy-z-każdym. + Problem FC: DUŻO parametrów (np. 25088 × 4096 = 102M wag w VGG-16!) + +**Forward pass (przejście w przód)** — JEDNO przetworzenie danych przez sieć od wejścia do wyjścia. Obraz wchodzi → przechodzi przez Conv, Pool, FC → wychodzi predykcja. Nie aktualizuje wag (to backward pass / backpropagation = uczenie). + + Forward pass CNN (czasy na GPU): + Jeden obraz przez ResNet-50: ~5ms + R-CNN: 2000 regionów × 5ms = 10 SEKUND (dlatego był wolny!) + Fast R-CNN: 1 forward pass cały obraz + ROI Pool = ~200ms (50× szybciej!) + +**ReLU (Rectified Linear Unit)** — funkcja aktywacji: f(x) = max(0, x). Przepuszcza wartości dodatnie, zeruje ujemne. Standard w CNN — stosowana PO KAŻDEJ warstwie konwolucyjnej. + + Wejście: [-3, 5, -1, 2, 0, -7, 4] + ReLU: [ 0, 5, 0, 2, 0, 0, 4] + + Dlaczego potrzebna? Bez ReLU sieć = seria mnożeń macierzy = JEDNA liniowa + transformacja → nie potrafi uchwycić złożonych wzorców. + ReLU dodaje NIELINIOWOŚĆ → sieć aproksymuje DOWOLNĄ funkcję. + +**Softmax** — funkcja na WYJŚCIU klasyfikatora: zamienia surowe wyniki (logits) na prawdopodobieństwa sumujące się do 1. + + Logits: [2.0, 1.0, 0.1] + Softmax: [0.66, 0.24, 0.10] ← e^2.0 / (e^2.0 + e^1.0 + e^0.1) ≈ 0.66 + Klasy: ["kot", "pies", "ryba"] + → „66% szans, że to kot" + +**Tensor** — wielowymiarowa tablica liczb. Uogólnienie wektora i macierzy. + + Skalar = 0D tensor: 5 + Wektor = 1D: [1, 2, 3] + Macierz = 2D: [[1,2],[3,4]] + Obraz RGB = 3D: [224 × 224 × 3] ← wysokość × szerokość × kanały + Batch obrazów = 4D: [32 × 224 × 224 × 3] ← 32 obrazy naraz + Wyjście YOLO = 3D: [7 × 7 × 30] ← siatka × predykcje + +**Architektura CNN — pełny przykład (AlexNet, wygrał ImageNet 2012):** + +![CNN — od obrazu do predykcji](img/q24_cnn_architecture.png) + + ROZMIARY MALEJĄ: 224 → 55 → 27 → 13 → 6 (kompresja przestrzenna) + KANAŁY ROSNĄ: 3 → 96 → 256 → 384 → 256 (coraz więcej wyuczonych cech) + +--- + +**Backbone (kręgosłup / sieć bazowa)** — duża, pretrenowana sieć CNN (np. ResNet-50, VGG-16) używana jako „ekstraktor cech". Backbone przetwarza obraz → feature mapa. Na wierzch dodaje się GŁOWICĘ (head) specyficzną dla zadania. + + Analogia: backbone = SILNIK samochodu, head = KAROSERIA. + Ten sam silnik (ResNet) w różnych karoseriach: + Sedan → klasyfikacja: FC head → "kot" + SUV → detekcja: RPN + ROI Pool head → bbox + klasa + Pickup → segmentacja: dekoder head → maska pikseli + + Backbone PRETRENOWANY na ImageNet (miliony obrazów). + Head TRENOWANY od zera na konkretnym zadaniu (detekcja, segmentacja). + +**Detection head (głowa detekcyjna)** — warstwy dodane NA WIERZCH backbone'u. Predykują klasy obiektów + pozycje bbox. W Faster R-CNN: RPN + ROI Pool + FC. W YOLO: warstwy conv + wyjście S×S×(B×5+C). + +**ResNet, VGG, AlexNet — popularne backbone'y:** + + Sieć Rok Warstw Parametrów Top-5 ImageNet Innowacja + ───────────────────────────────────────────────────────────────────── + AlexNet 2012 8 60M 84.7% Pierwsza głęboka CNN + VGG-16 2014 16 138M 92.7% Małe filtry 3×3 + ResNet-50 2015 50 25M 96.4% Skip connections + + Mnemonik: A → V → R = „Architektura Bardzo Rezylientna" (2012 → 2014 → 2015) + + Skip connection (ResNet): y = F(x) + x + Wejście bloku DODAWANE do wyjścia → gradient nie zanika + → można trenować 50-152 warstw (bez skip: >20 warstw = DEGRADACJA!) + +**ImageNet** — ogromny zbiór danych: 14M obrazów, 1000 klas (pies, samolot, gitara...). Standard pretrenowania w computer vision. ILSVRC (coroczne zawody) — AlexNet wygrał 2012 → rewolucja deep learning. + +**Transfer learning (uczenie transferowe)** — weź sieć pretrenowaną na dużym zbiorze (ImageNet), użyj do INNEGO zadania (detekcja, segmentacja). Backbone „wie" jak wyglądają krawędzie i kształty — trzeba tylko nauczyć nowej głowicy. + + Krok po kroku: + 1. ResNet-50 pretrenowany na ImageNet (1000 klas, miliony obrazów) + 2. Odtnij warstwę FC (klasyfikujse 1000 klas ImageNet) ← WYRZUĆ + 3. Dodaj nową głowicę detekcji (bbox + 80 klas COCO) ← NOWA + 4. Trenuj głowicę na danych detekcyjnych (COCO/VOC) + 5. Opcjonalnie: fine-tune = odmroź backbone, ucz z MAŁYM learning rate + + Dlaczego działa? Cechy niskiego poziomu (krawędzie, tekstury) SĄ UNIWERSALNE. + Kot, samochód, twarz — wszystko ma krawędzie i tekstury! + +**Fine-tuning (dostrajanie)** — forma transfer learning: odmrażasz backbone i uczysz CAŁĄ sieć z MAŁYM learning rate, żeby subtelnie dopasować cechy do nowego zadania. + +**COCO (Common Objects in Context)** — benchmark detekcji: 330K obrazów, 80 klas (samochód, osoba, pies...), 1.5M bboxów. Standard oceny detektorów. + +**Pascal VOC (Visual Object Classes)** — starszy benchmark: 20 klas. Używany w oryginalnym YOLO i R-CNN. + +**mAP (mean Average Precision)** — główna metryka jakości detekcji. Łączy trafność klasy z trafnością lokalizacji. + + mAP@0.5: detekcja „trafna" jeśli IoU ≥ 0.5 (≥50% pokrycia z prawdą) + mAP@0.5:0.95: średnia po progach 0.5, 0.55, ..., 0.95 (dużo surowsza) + + Faster R-CNN (COCO): mAP ≈ 42% + YOLOv8-X (COCO): mAP ≈ 53% + +**End-to-end (od końca do końca)** — cała sieć trenowana jako JEDNOŚĆ, jeden loss, jeden trening. Przeciwieństwo: R-CNN miał ODDZIELNIE Selective Search + CNN + SVM = 3 osobne kroki. Faster R-CNN = end-to-end → komponenty uczą się WSPÓŁPRACOWAĆ → lepsze wyniki. + +**FPN (Feature Pyramid Network)** — technika łączenia feature map z RÓŻNYCH warstw backbone'u. Wczesne warstwy (wysoka rozdzielczość) → małe obiekty. Późne warstwy (niska rozdzielczość) → duże obiekty. FPN łączy obie → wykrywa obiekty WSZYSTKICH rozmiarów. + +![FPN (Feature Pyramid Network)](img/q24_fpn.png) + +--- + +**Klasyfikator (classifier)** — model przypisujący etykietę do wejścia. Np. CNN trenowany na ImageNet: obraz → „kot" (+ prawdopodobieństwo). Klasyfikator nie mówi GDZIE jest obiekt — mówi tylko CO jest na obrazie. Pytanie brzmi: jak z takiego modelu zbudować detektor? **Sliding window (okno przesuwane)** — najprostsza metoda budowy detektora z klasyfikatora: wytnij prostokątny fragment obrazu (wiele rozmiarów, wiele pozycji), każdy fragment sklasyfikuj. Jeśli „pozytywny" → detekcja. Ekstremalnie wolne: tysiące fragmentów × klasyfikacja per fragment. @@ -5663,15 +6149,7 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **Support Vectors** — punkty danych NAJBLIŻSZE hiperpłaszczyźnie. To one „podpierają" (support) margines i definiują pozycję hiperpłaszczyzny. Reszta punktów jest nieistotna! Nazwa: „wektory nośne" — bo to wektory cech, które „niosą" decyzję. - Przestrzeń 2D: O = klasa "pie szy" X = klasa "nie-pieszy" - O O - O O - hiperpłaszczyzna → ─ ─ ─ ─ ─ ─ ─ ─ ← margines ↕ - X X - X X X - - Support vectors: O i X najbliższe linii (zaznaczone pogrubione) - SVM: przesuń linię tak, żeby margines ↕ był MAKSYMALNY +![SVM — hiperpłaszczyzna i margines](img/q24_svm_hyperplane.png) **HOG+SVM — klasyczny pipeline detekcji pieszych:** @@ -5684,18 +6162,95 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy 3. NMS (Non-Maximum Suppression) → usuń duplikaty 4. Wynik: lista bounding boxów z detekcjami pieszych -**Viola-Jones (2001)** — przełomowy detektor twarzy real-time. Kluczowe innowacje: -- **Haar features** — proste cechy prostokątne (jasne/ciemne regiony) -- **Integral Image** — obliczenie dowolnej sumy prostokąta w O(1)! -- **AdaBoost cascade** — kaskada klasyfikatorów: szybkie odrzucenie 99% okien w pierwszych etapach, szczegółowa analiza tylko obiecujących +**Viola-Jones (2001)** — przełomowy detektor twarzy w CZASIE RZECZYWISTYM. Trzy kluczowe innowacje wyjasnione szczegółowo: + +**Haar features (cechy Haarowe)** — najprostsze cechy obrazowe: prostokąty podzielone na jasną i ciemną część. Wartość cechy = (suma pikseli jasnych) − (suma pikseli ciemnych). Proste, ale skuteczne — wykrywają kontrasty typowe dla twarzy. + +![Cechy Haar — typy i zastosowanie na twarzy](img/q24_haar_features.png) + + Dlaczego działa na TWARZACH? + - Oczy CIEMNIEJSZE niż czoło → cecha "krawędź pozioma" daje dużą wartość + - Nos JAŚNIEJSZY niż policzki → cecha "linia pionowa" daje dużą wartość + - Twarz = charakterystyczna KOMBINACJA takich kontrastów! + + Ile cech? W oknie 24×24 pikseli: ponad 160 000 możliwych cech Haar + (różne rozmiary × różne pozycje). AdaBoost wybiera ~200 NAJLEPSZYCH. + +**Integral Image (obraz całkowy)** — precomputed tabela pozwalająca obliczyć sumę pikseli w DOWOLNYM prostokącie w O(1) — stały czas, niezależnie od rozmiaru! To dlatego Haar features liczą się tak szybko. + + Jak? Integral Image[x,y] = suma WSZYSTKICH pikseli od (0,0) do (x,y). + +![Integral Image — suma prostokąta w O(1)](img/q24_integral_image.png) + + Zawsze 4 odczyty z tabeli → O(1)! + Czy prostokąt ma 4 piksele czy 4 MILIONY — czas TEN SAM! + Bez Integral Image: O(w×h) — suma 1000×1000 = milion operacji. + Z Integral Image: O(1) — 4 operacje. ZAWSZE. + + Pseudokod: + def integral_image(img): + II = zeros_like(img) + for y in range(H): + for x in range(W): + II[y][x] = img[y][x] + II[y-1][x] + II[y][x-1] - II[y-1][x-1] + return II + + def rect_sum(II, x1, y1, x2, y2): # O(1) zawsze! + return II[y2][x2] - II[y1-1][x2] - II[y2][x1-1] + II[y1-1][x1-1] + +**AdaBoost (Adaptive Boosting)** — algorytm uczenia maszynowego łączący wiele SŁABYCH klasyfikatorów w jeden SILNY. Słaby = niewiele lepszy od losowego (>50% trafień). AdaBoost iteracyjnie: +1. Trenuj słaby klasyfikator (np. 1 cecha Haar + próg: "czy wartość > 1200?") +2. Sprawdź, które przykłady ŹLE sklasyfikował +3. Nadaj źle sklasyfikowanym WIĘKSZĄ wagę → następny klasyfikator SKUPI się na nich +4. Powtórz 200× → suma ważona 200 słabych klasyfikatorów ≈ silny klasyfikator + + Intuicja: jak PANEL EKSPERTÓW, z których każdy zna się na JEDNEJ rzeczy. + Ekspert 1: "czy okolice oczu ciemne?" (trafność 55%) + Ekspert 2: "czy nos jaśniejszy niż policzki?" (trafność 60%) + Ekspert 3: "czy brwi ciemne?" (trafność 53%) + ... + 200 ekspertów razem → trafność >95%! + Mnemonik: AdaBoost = "ADAptacyjnie BOOSTuj" słabe modele do silnego. + +**Cascade (kaskada klasyfikatorów)** — genialna optymalizacja szybkości: zamiast sprawdzać WSZYSTKIE 200 cech na każdym oknie, użyj KASKADY etapów. Każdy etap = prosty klasyfikator, który szybko ODRZUCA "na pewno nie-twarz". + +![Viola-Jones — kaskada klasyfikatorów (SITO)](img/q24_viola_jones_cascade.png) + + Mnemonik: kaskada = "SITO" — coraz drobniejsze oczka, + na początku odpada piach, na końcu zostaje ZŁOTO (twarz). + + Pseudokod kaskady: + def cascade_classify(window): + for stage in cascade_stages: # etap 1, 2, ..., 25 + score = stage.evaluate(window) # oblicz kilka cech Haar + if score < stage.threshold: # za niski wynik + return "NIE-TWARZ" # SZYBKIE odrzucenie! + return "TWARZ" # przeszło WSZYSTKIE etapy --- -![Ewolucja detektorów: R-CNN → Faster R-CNN → YOLO](img/rcnn_evolution.png) +![Ewolucja detektorów: R-CNN → Faster R-CNN → YOLO](img/q24_rcnn_evolution.png) -**R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. +**R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. Nazwa: Region-based CNN. -**Czym jest „region proposal" (propozycja regionu)?** — prostokąt, w którym MOŻE BYĆ obiekt. Zamiast sprawdzać miliony pozycji okna (sliding window), algorytm propozycji generuje ~2000 „obiecujących" prostokątów. Jak? Metoda Selective Search analizuje kolory, tekstury i rozmiary → łączy podobne regiony → generuje kandydatów. +**Selective Search (wyszukiwanie selektywne)** — klasyczny algorytm (NIE sieć neuronowa!) generowania propozycji regionów. Zamiast MILIONÓW pozycji okna (sliding window), inteligentnie łączy podobne fragmenty obrazu i proponuje ~2000 prostokątów, w których MOGĄ być obiekty. + + Algorytm krok po kroku: + 1. Over-segmentation: podziel obraz na ~1000 małych regionów (superpixele) + (na podstawie koloru i tekstury — algorytm Felzenszwalb) + 2. Powtarzaj aż zostanie 1 region: + a) Znajdź 2 najbardziej PODOBNE sąsiednie regiony: + - podobny kolor? (histogram kolorów) + - podobna tekstura? (histogram gradientów) + - pasujący rozmiar? (preferuj łączenie MAŁYCH regionów) + b) Połącz je w jeden → zapamiętaj bounding box nowego regionu + 3. Zebrane bbox-y ze WSZYSTKICH kroków → ~2000 propozycji + + Sliding window: ~500 000 okien → 99.9% to "tło" → marnujesz czas + Selective Search: ~2 000 regionów → ~50% zawiera coś → 250× wydajniej + RPN (Faster R-CNN): ~300 propozycji → sieć neuronowa (najszybciej!) + +**Czym jest "region proposal" (propozycja regionu)?** — prostokąt, w którym MOŻE być obiekt. Dużo mniej niż sliding window (2000 zamiast milionów), ale każda propozycja ma WYSOKIE prawdopodobieństwo trafienia obiektu. **R-CNN (2014, Ross Girshick)** — pierwszy detektor oparty na CNN. Pipeline: @@ -5711,26 +6266,84 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy Dlaczego tak wolno? Bo CNN liczy cechy na KAŻDYM wyciętym regionie OSOBNO, choć regiony się częściowo nakładają → redundantne obliczenia -**Fast R-CNN (2015)** — kluczowa optymalizacja: przepuść cały obraz przez CNN RAZ, uzyskaj „mapę cech" (feature map). Potem wytnij cechy regionów z tej mapy (ROI Pooling), zamiast odpalać CNN 2000 razy. +**Fast R-CNN (2015)** — kluczowa optymalizacja: przepuść cały obraz przez CNN RAZ, uzyskaj "mapę cech" (feature map). Potem wytnij cechy regionów z tej mapy (ROI Pooling), zamiast odpalać CNN 2000 razy. - Dlaczego „ROI Pooling"? ROI = Region of Interest. Regiony mają RÓŻNE rozmiary, - ale warstwa FC wymaga stałego. ROI Pooling dzieli region na siatkę np. 7×7 - i w każdej komórce bierze MAX → stały rozmiar wyjścia niezależnie od wejścia. +**ROI (Region of Interest, region zainteresowania)** — prostokątny fragment feature mapy odpowiadający propozycji regionu na oryginalnym obrazie. Np. Selective Search zaproponował bbox (100,50)-(200,150) na obrazie 800×600 → odpowiadający ROI na feature mapie (po redukcji 16× przez pooling) to mniej więcej (6,3)-(12,9). - CNN raz na obraz → feature map → ROI Pool 2000 regionów → FC → klasy + bbox - Przyspieszenie: ~2 sec/obraz (vs 50 sec w R-CNN) +**ROI Pooling (pooling regionu zainteresowania)** — operacja zamieniająca ROI o DOWOLNYM rozmiarze na tensor o STAŁYM rozmiarze (np. 7×7). Konieczne, bo warstwa FC wymaga stałego rozmiaru wejścia! -**Faster R-CNN (2015)** — ostatni krok: zastąp Selective Search (osobny algorytm) siecią neuronową! **RPN (Region Proposal Network)** — mała sieć przesuwana po feature mapie, która w KAŻDEJ pozycji predykuje: „czy tu jest obiekt?" + proponuje bbox. Wszystko w jednej sieci, end-to-end. + Problem: region 1 = 14×10 na feature mapie, region 2 = 8×6 → RÓŻNE! + Warstwa FC wymaga np. 7×7 → STAŁY rozmiar. + Rozwiązanie — ROI Pooling: + 1. Weź ROI (np. 14×10) z feature mapy + 2. Podziel go na siatkę 7×7 (= 7 wierszy × 7 kolumn) + Każda komórka obejmuje ok. 2×1.4 pikseli feature mapy + 3. W każdej komórce weź MAX (jak max pooling) + 4. Wynik: tensor 7×7 — STAŁY rozmiar niezależnie od oryginalnego ROI! + +![ROI Pooling](img/q24_roi_pooling.png) + + Kluczowa sztuczka Fast R-CNN: + CNN raz na CAŁY obraz → JEDNA feature mapa → ROI Pool 2000 regionów z TEJ SAMEJ mapy + (zamiast 2000× odpalać CNN jak w R-CNN!) + Przyspieszenie: ~2 sec/obraz (vs 50 sec) → 25× szybciej! + + Pseudokod ROI Pooling: + def roi_pool(feature_map, roi_bbox, output_size=7): + roi = feature_map[roi_bbox] # wycinek z feature mapy + h, w = roi.shape + cell_h, cell_w = h // output_size, w // output_size + output = zeros(output_size, output_size) + for i in range(output_size): + for j in range(output_size): + cell = roi[i*cell_h:(i+1)*cell_h, j*cell_w:(j+1)*cell_w] + output[i][j] = max(cell) # max pooling w komórce + return output # stały rozmiar 7×7! + + CNN raz → feature map → ROI Pool 2000 regionów → FC → klasy + bbox + +**Bbox regression (regresja prostokąta ograniczającego)** — sieć predykuje nie bezpośrednie współrzędne bbox, ale PRZESUNIĘCIA (offsets) od propozycji: Δx, Δy (przesunięcie środka), Δw, Δh (zmiana szerokości/wysokości). + + Propozycja (z RPN/Selective Search): (x=100, y=80, w=60, h=90) ← przybliżone + Predykcja regresji: (Δx=+5, Δy=-3, Δw=+10, Δh=+5) + Ostateczny bbox: (x=105, y=77, w=70, h=95) ← dokładniejsze! + + Dlaczego offsets a nie współrzędne bezpośrednio? + Łatwiejsze zadanie! Sieć poprawia przybliżony prostokąt O TROCHĘ, + zamiast zgadywać lokalizację od zera. + Mnemonik: bbox regression = "GPS korekta" — masz przybliżoną pozycję, + poprawiasz o parę metrów w prawo i w górę. + +**Faster R-CNN (2015)** — ostatni krok ewolucji: zastąp Selective Search (osobny algorytm) siecią neuronową! **RPN (Region Proposal Network)** — mała sieć przesuwana po feature mapie, która w KAŻDEJ pozycji predykuje: "czy tu jest obiekt?" + proponuje bbox. Wszystko w jednej sieci, end-to-end. + + Pipeline Faster R-CNN: Obraz → CNN backbone (np. ResNet) → Feature Map → RPN (proposals) → ROI Pool → FC → klasy + bbox - RPN szczegóły: - - W każdym punkcie feature mapy rozważ k=9 „anchor boxes" (3 rozmiary × 3 proporcje) - - Dla każdego anchora: P(obiekt) + przesunięcie bbox (Δx, Δy, Δw, Δh) - - Zachowaj ~300 propozycji z najwyższym P(obiekt) → do ROI Pool + RPN krok po kroku: + Feature mapa [40×60×256] ← z backbone + ↓ Filtr 3×3 przesuwa się po feature mapie + ↓ W KAŻDEJ pozycji (x,y) rozważ k=9 "anchor boxes": + + 9 anchorów = 3 rozmiary × 3 proporcje: + ┌───┐ ┌─────┐ ┌───────┐ ← 128×128, 256×256, 512×512 + │ │ │ │ │ │ × proporcje 1:1, 1:2, 2:1 + └───┘ └─────┘ └───────┘ + + ↓ Dla KAŻDEGO z 9 anchorów sieć predykuje: + - P(obiekt) = prawdopodobieństwo, że tu jest obiekt + - (Δx, Δy, Δw, Δh) = przesunięcie bbox względem anchora + + 40×60 = 2400 pozycji × 9 anchorów = 21 600 potencjalnych propozycji! + → Weź ~300 z najwyższym P(obiekt) → ROI Pool → FC → klasy + bbox Faster R-CNN: ~5 fps (~0.2 sec/obraz) — 250× szybciej niż R-CNN! + Mnemonik ewolucji R-CNN: "CORAZ MNIEJ MARNOWANIA" + R-CNN: Selective Search + 2000×CNN = 50s → WOLNE + Fast R-CNN: Selective Search + 1×CNN + ROI Pool = 2s → lepiej + Faster R-CNN: RPN (w sieci!) + 1×CNN + ROI Pool = 0.2s → 250× szybciej! + --- **One-stage detectors** — klasyfikacja i lokalizacja w jednym przejściu (bez osobnego etapu propozycji). Szybsze, ale historycznie mniej precyzyjne. @@ -5740,45 +6353,127 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy - C prawdopodobieństw klas = „jaki to obiekt?" Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps! - Jak to działa wizualnie (S=7, B=2, C=20 klas jak w Pascal VOC): - - Obraz [448×448] → CNN (24 warstwy konwolucyjne + 2 FC) → tensor 7×7×30 - ↑ - 30 = 2×(4+1) + 20 - 2 bbox × (x,y,w,h,conf) + 20 klas - - Komórka (3,4) predykuje: bbox1=(0.3, 0.7, 0.4, 0.6, 0.92), klasa="samochód" (p=0.88) - → „środek samochodu jest w komórce (3,4), bbox ma takie wymiary, pewność 92%" - - Potem NMS: usuwa duplikaty (wiele komórek może wykryć ten sam obiekt) +![YOLO — detekcja jednoetapowa (siatka S×S)](img/q24_yolo_grid.png) **SSD (Single Shot MultiBox Detector, 2016)** — ulepsza YOLO przez multi-scale feature maps: predykcje z WIELU warstw CNN, każda o innej rozdzielczości. Wczesne warstwy (wysoka rozdzielczość) wykrywają MAŁE obiekty; późne warstwy (niska rozdzielczość) wykrywają DUŻE. Anchor boxes predefiniowane na każdej skali. **Anchor box (kotwica)** — predefiniowany prostokąt o określonym kształcie/proporcji (np. 1:1, 1:2, 2:1). Sieć NIE predykuje bbox od zera — predykuje PRZESUNIĘCIE (offset) od najbliższego anchora. Łatwiejsze zadanie! Wiele anchorów → pokrycie różnych kształtów obiektów (osoby = wysoki prostokąt, samochód = szeroki). +![Anchor boxes — predefiniowane kształty](img/q24_anchor_boxes.png) + **Anchor-free** — nowoczesne podejście (FCOS, YOLOv8): bezpośrednia predykcja środka i wymiarów, bez predefiniowanych anchorów. Prostsza architektura, mniej hyperparametrów. -**DETR (DEtection TRansformer, 2020)** — Facebook AI. Zamiast CNN + anchor + NMS, używa **transformera** z mechanizmem self-attention. Predykuje bezpośrednio ZESTAW obiektów (set prediction, nie grid). NIE potrzebuje NMS (unik duplikatów rozwiązany przez Hungarian matching w treningu). Najprostsza architektura w detekcji, ale wolniejsza w treningu. +**Transformer** — architektura sieci neuronowej pierwotnie z NLP (2017, "Attention is All You Need"), ale skutecznie zaadaptowana do wizji komputerowej (ViT, DETR). Kluczowy mechanizm: **self-attention** — każdy element wejścia "patrzy" na WSZYSTKIE inne elementy i decyduje, które są dla niego ważne. + + W tekście: słowo "bank" patrzy na "rzeka" i "pieniądze" → + attention decyduje: "w tym zdaniu chodzi o brzeg RZEKI, nie bank pieniędzy" + + W obrazie (DETR): fragment obrazu "patrzy" na inne fragmenty → + attention: "ta łapa jest częścią TEGO kota, a nie tamtego psa" + +**Self-attention (samo-uwaga)** — mechanizm: dla każdego elementu oblicz "uwagę" do KAŻDEGO innego elementu. Matematycznie: Query × Key → wagi attention → ważona suma Values. + + Uproszczony pseudokod: + def self_attention(features): # features = N elementów + Q = features × W_query # Query: "czego szukam?" + K = features × W_key # Key: "co oferuję?" + V = features × W_value # Value: "jaką informację niosę?" + + attention = softmax(Q × K^T / sqrt(d)) # macierz N×N: "kto ważny dla kogo" + output = attention × V # ważona kombinacja wartości + return output + + Złożoność: O(n^2) — każdy element z każdym → wolne dla dużych obrazów. + Dlatego DETR wolniej się TRENUJE niż YOLO (ale architektura jest PROSTSZA). + +**DETR (DEtection TRansformer, 2020)** — model Facebooka stosujący Transformer do detekcji. Radykalnie prostszy pipeline: BRAK anchorów, BRAK NMS! Sieć predykuje bezpośrednio ZESTAW N obiektów (np. N=100). + +![DETR — Transformer do detekcji](img/q24_detr_pipeline.png) + + "Object queries" = 100 wyuczonych wektorów, każdy "szuka" jednego obiektu. + Obraz z 5 obiektami → 5 queries dopasuje się do obiektów, + 95 queries zwróci klasę "brak obiektu" (empty set). + + Pseudokod DETR: + def detr_forward(image): + features = backbone(image) # ResNet → feature mapa + encoded = transformer_encoder(features) # self-attention na feat. mapie + queries = learnable_queries(100) # 100 wyuczonych zapytań + decoded = transformer_decoder(queries, encoded) # cross-attention + predictions = [] + for q in decoded: + cls = classify(q) # "samochód" / "pies" / "brak" + box = regress(q) # (x, y, w, h) + predictions.append((cls, box)) + return predictions # 100 predykcji (większość = brak) + + Mnemonik DETR: "Detekcja Eliminująca Trikowe Redundancje" + → bez NMS, bez anchorów, prosty pipeline. + +**Hungarian matching (dopasowanie węgierskie)** — algorytm używany podczas TRENINGU DETR. Problem: sieć daje 100 predykcji, na obrazie jest 5 obiektów — która predykcja odpowiada któremu obiektowi? Algorytm węgierski znajduje OPTYMALNE dopasowanie 1:1 minimalizując łączny koszt (błąd klasy + błąd bbox). + + Predykcje DETR: Ground truth: + pred_1: "samochód" gt_1: "samochód" (bbox A) + pred_2: "pies" gt_2: "pies" (bbox B) + pred_3: "brak" + ... Hungarian matching: + pred_100: "brak" pred_1 ↔ gt_1 (najlepsze dopasowanie!) + pred_2 ↔ gt_2 + reszta ↔ "brak obiektu" + + Efekt: BRAK DUPLIKATÓW → BRAK NMS! + (Każdy obiekt dopasowany do DOKŁADNIE jednej predykcji) --- -**NMS (Non-Maximum Suppression)** — post-processing: detektor generuje wiele nakładających się bbox dla tego samego obiektu. NMS: weź najlepszą (max confidence), usuń wszystkie mocno nakładające się (IoU > prog), powtórz. +**NMS (Non-Maximum Suppression, tłumienie nie-maksymalnych)** — algorytm post-processingu usuwający ZDUPLIKOWANE detekcje. Problem: detektor generuje WIELE nakładających się bbox dla tego samego obiektu. NMS zachowuje NAJLEPSZĄ i usuwa resztę. Jedyny detektor BEZ NMS = DETR. - Detections: [bbox1, 0.95], [bbox2, 0.90], [bbox3, 0.85] (nakładające się) - NMS: zachowaj bbox1 (0.95), usuń bbox2 i bbox3 (IoU > 0.5 z bbox1) + Algorytm NMS krok po kroku: + Wejście: detekcje posortowane malejąco po confidence + [bbox_1 conf=0.95], [bbox_2 conf=0.90], [bbox_3 conf=0.85], [bbox_4 conf=0.40] -**IoU (Intersection over Union)** — miara nakładania dwóch bbox: pole przecięcia / pole sumy. IoU=1 → identyczne; IoU=0 → brak nakładania. Próg NMS typowo 0.5. + Pseudokod NMS: + def nms(detections, iou_threshold=0.5): + detections.sort(by=confidence, descending=True) + keep = [] + while detections: + best = detections.pop(0) # weź najlepszą + keep.append(best) # ZACHOWAJ ją + detections = [d for d in detections + if iou(best, d) < iou_threshold] # usuń nakładające + return keep -**Backbone** — sieć bazowa (np. ResNet, VGG) wyciągająca cechy z obrazu. Detection head (głowa detekcyjna) jest dodawana „na wierzch" backbone i predykuje bbox + klasy. Fine-tuning backbone na detekcję = transfer learning. + Krok 1: Weź bbox_1 (0.95) → ZACHOWAJ + Krok 2: IoU(bbox_1, bbox_2) = 0.82 > 0.5 → USUŃ (duplikat tego samego kota!) + IoU(bbox_1, bbox_3) = 0.75 > 0.5 → USUŃ (duplikat!) + IoU(bbox_1, bbox_4) = 0.10 < 0.5 → ZACHOWAJ (INNY obiekt!) + Krok 3: Wynik: [bbox_1, bbox_4] — 2 unikalne obiekty + + Mnemonik: NMS = "Najlepszy Ma Się dobrze" — zachowaj najlepszą, usuń resztę. + +**IoU (Intersection over Union)** — miara nakładania dwóch prostokątów. IoU = pole przecięcia / pole sumy. Wartości: 0.0 (nie nakładają się) do 1.0 (identyczne). + +![IoU (Intersection over Union)](img/q24_iou_diagram.png) + + IoU = pole(∩) / pole(A ∪ B) + = pole(∩) / (pole(A) + pole(B) − pole(∩)) + + Przykład liczbowy: + A = [0, 0, 100, 100] → pole = 10 000 + B = [50, 50, 150, 150] → pole = 10 000 + ∩ = [50, 50, 100, 100] → pole = 2 500 + IoU = 2500 / (10000 + 10000 − 2500) = 2500 / 17500 ≈ 0.14 + + IoU > 0.5 w NMS → "to TEN SAM obiekt" → usuń słabszą detekcję + IoU > 0.5 w mAP → "detekcja TRAFNA" → poprawna lokalizacja --- -**Jak zbudować detektor z klasyfikatora? Trzy podejścia:** -1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne. -2. **Region proposals + klasyfikator** — Selective Search generuje ~2000 regionów, sklasyfikuj każdy + NMS. Szybsze. -3. **Fine-tune backbone** — weź pretrained classifier (np. ResNet z ImageNet), dodaj detection head (bbox regression + cls), dotrenuj na danych detekcyjnych. **Najlepsza jakość.** - -**DETR (DEtection TRansformer, 2020)** — Facebook AI. Transformer zamiast CNN, bezpośrednia predykcja zestawu obiektów (set prediction), bez NMS. Uproszczona architektura. +**Jak zbudować detektor z klasyfikatora? Trzy podejścia (+ bonus):** +1. **Sliding window** — wytnij, sklasyfikuj, NMS. Bardzo wolne (miliony klasyfikacji). +2. **Region proposals + klasyfikator** — Selective Search → ~2000 regionów → klasyfikuj + NMS. Wolne ale działa (= R-CNN). +3. **Fine-tune backbone** — weź pretrained classifier (ResNet z ImageNet), dodaj detection head (bbox regression + cls), dotrenuj na danych detekcyjnych. **Najlepsza jakość** (= Faster R-CNN, YOLO, SSD). +4. **Transformer (DETR)** — bez anchorów, bez NMS, predykcja zestawu obiektów end-to-end. --- @@ -5793,11 +6488,7 @@ Detekcja obiektów to **lokalizacja** (gdzie?) i **klasyfikacja** (co?) obiektó **Porównanie z innymi zadaniami:** - Zadanie Wynik Przykład - ───────────────────────────────────────────────────────── - Klasyfikacja "kot" (1 etykieta) cały obraz → 1 klasa - Detekcja bbox + klasa (N obiektów) prostokąty wokół obiektów - Segmentacja etykieta per piksel maska pikseli +![Klasyfikacja vs Detekcja vs Segmentacja](img/q24_detection_tasks.png) --- @@ -5810,21 +6501,134 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc | **HOG + SVM** | 2005 | Histogram of Oriented Gradients | SVM | wolna (~1 fps) | detekcja pieszych | | **Viola-Jones** | 2001 | Haar features + Integral Image | AdaBoost cascade | real-time (30+ fps) | detekcja twarzy | -**HOG + SVM (Dalal & Triggs, 2005):** +#### HOG + SVM (Dalal & Triggs, 2005) — krok po kroku - Pipeline: Obraz → Sliding window → HOG (histogramy gradientów) → SVM → detekcja/brak - HOG: dzieli okno na komórki (8×8 px), liczy histogramy kierunków krawędzi - SVM: "czy ten wzorzec krawędzi to człowiek?" - Wada: ręczne cechy, wolny sliding window, działa dobrze TYLKO na pieszych +**Mnemonik kroków HOG: „GÓRA KOCHA BOGATYCH NARCIARZY" → Gradienty → Orientacja → Komórki → Bloki → Normalizacja** -**Viola-Jones (2001) — 3 innowacje:** +![HOG + SVM pipeline detekcji pieszych](img/q24_hog_svm_pipeline.png) - 1. Haar features: [ jasne | ciemne ] → prosta różnica intensywności - 2. Integral Image: suma prostokąta w O(1), niezależnie od rozmiaru! - 3. Cascade: Etap 1 (2 cechy): odrzuca 50% okien w 1 μs - Etap 2 (10 cech): odrzuca 80% reszty - ...Etap 25 (200 cech): szczegółowa analiza TYLKO 0.01% okien - Efekt: ~95% detections = szybkie odrzucenia → real-time! +**Krok 1 — Gradienty (G jak GÓRA):** Oblicz gradient KAŻDEGO piksela. Gradient = „siła i kierunek zmiany jasności". Tam, gdzie jasność skacze (np. 50→200), jest krawędź. + + Przykład liczbowy: + Piksele w wierszu: [50, 50, 200] + Gx = pixel[x+1] − pixel[x−1] = 200 − 50 = 150 ← silna krawędź pionowa! + Gy = analogicznie w pionie + Siła: magnitude = √(Gx² + Gy²) = √(150² + 0²) = 150 + Kierunek: direction = arctan(Gy/Gx) = arctan(0/150) = 0° (krawędź pionowa) + +**Krok 2 — Orientacja (O jak KOCHA):** Każdy piksel głosuje na kierunek swojej krawędzi. 9 „koszyków" (binów) co 20°: 0°, 20°, 40°, …, 160°. Głos ważony SIŁĄ gradientu (silniejsza krawędź = mocniejszy głos). + + Piksel z magnitude=150, direction=10°: + Głosuje na bin 0° (z wagą proporcjonalną do bliskości) i bin 20° + Piksel z magnitude=30, direction=85°: + Głosuje na bin 80° i bin 100° (słabsza krawędź = słabszy głos) + +**Krok 3 — Komórki (K jak BOGATYCH):** Podziel okno (64×128 px) na komórki 8×8 pikseli = 8×16 = 128 komórek. Dla KAŻDEJ komórki stwórz histogram 9 binów — to jej „odcisk palca kierunkowości krawędzi". + +![HOG — kroki obliczania cech](img/q24_hog_gradient_steps.png) + +**Krok 4 — Bloki (B jak NARCIARZY):** Grupuj komórki w bloki 2×2 (= 16×16 px). Przesuwaj blok z krokiem 1 komórki. Okno 64×128 → (8−1)×(16−1) = 7×15 = 105 bloków. + +**Krok 5 — Normalizacja (N):** Dla KAŻDEGO bloku (4 komórki × 9 binów = 36 wartości) wykonaj normalizację L2 → odporność na zmiany oświetlenia. 105 bloków × 36 = **3780 cech** → wektor HOG. + + Pseudokod: + def compute_hog(window_64x128): + Gx = pixel[x+1] - pixel[x-1] # gradient poziomy + Gy = pixel[y+1] - pixel[y-1] # gradient pionowy + mag = sqrt(Gx**2 + Gy**2) # siła + dir = arctan2(Gy, Gx) * 180 / pi # kierunek 0°-180° + + hog = [] + for block_2x2 in sliding_blocks(cells_8x8): + block_hist = [] + for cell in block_2x2: # 4 komórki + hist = [0]*9 # 9 binów + for px in cell.pixels: # 64 piksele + bin = int(dir[px] / 20) # który bin? + hist[bin] += mag[px] # ważone głosowanie + block_hist += hist + block_hist = L2_normalize(block_hist) # normalizacja! + hog += block_hist + return hog # wektor 3780 cech → do SVM + +**Krok 6 — SVM klasyfikuje:** Wektor 3780 cech → SVM odpowiada: „pieszy" (+1) lub „tło" (−1). + +![SVM — hiperpłaszczyzna i margines](img/q24_svm_hyperplane.png) + + Mnemonik SVM: „LINIA MAKSYMALNEGO ODDECHU" + SVM = linia (hiperpłaszczyzna) z MAKSYMALNYM marginesem. + Jak MOST nad rzeką — im szerszy, tym bezpieczniejszy (lepiej generalizuje). + +**Krok 7 — NMS:** Usuń duplikaty (wiele okien wykryło tego samego pieszego → zachowaj najlepsze). + + Mnemonik PEŁNEGO pipeline'u HOG+SVM: „GOKBN-SN" + → Gradienty → Orientacja → Komórki → Bloki → Normalizacja → SVM → NMS + = „Grasz Ostro, Kumplu? Bądź Naturalny, Szybko Nabierz (wprawy)!" + +--- + +#### Viola-Jones (2001) — krok po kroku + +**Mnemonik 3 innowacji: „HIC" → Haar + Integral Image + Cascade** + +**Innowacja 1 — Haar features (H):** Prostokąty dzielone na jasną i ciemną część. Wartość = Σ(jasna) − Σ(ciemna). Proste, ale wykrywają kontrasty typowe dla twarzy. + +![Cechy Haar — typy i zastosowanie na twarzy](img/q24_haar_features.png) + + Pseudokod cechy Haar: + def haar_edge_vertical(img, x, y, w, h): + left_sum = sum_pixels(img, x, y, x+w//2, y+h) # jasna połówka + right_sum = sum_pixels(img, x+w//2, y, x+w, y+h) # ciemna połówka + return left_sum - right_sum # duża wartość = silna krawędź + + Mnemonik: Haar = „Hej, A tu jest Różnica?" + Cechy Haar pytają: „Czy lewa strona JAŚNIEJSZA niż prawa?" + +**Innowacja 2 — Integral Image (I):** Precomputed tabela: suma DOWOLNEGO prostokąta w O(1) — 4 odczyty z tabeli, niezależnie od rozmiaru! + +![Integral Image — suma prostokąta w O(1)](img/q24_integral_image.png) + + Pseudokod: + def build_integral_image(img): + II = zeros(H, W) + for y in range(H): + for x in range(W): + II[y][x] = img[y][x] + II[y-1][x] + II[y][x-1] - II[y-1][x-1] + return II + + def rect_sum(II, x1, y1, x2, y2): # ZAWSZE O(1)! + return II[y2][x2] - II[y1-1][x2] - II[y2][x1-1] + II[y1-1][x1-1] + + Mnemonik: Integral Image = „4 Odczyty I Gotowe!" = 4OIG + Jak czytanie z gotowej tabeli: nie liczymy, tylko odczytujemy! + +**Innowacja 3 — Cascade (C):** Kaskada etapów — szybkie odrzucanie „na pewno nie-twarz". + +![Viola-Jones — kaskada klasyfikatorów (SITO)](img/q24_viola_jones_cascade.png) + + Pseudokod: + def cascade_classify(window): + for stage in [stage_1, stage_2, ..., stage_25]: + score = sum(stage.weights[i] * haar_feature[i](window) + for i in stage.features) + if score < stage.threshold: + return "NIE-TWARZ" # szybkie odrzucenie! + return "TWARZ" # przeszło WSZYSTKIE etapy + + Mnemonik: Cascade = „SITO z coraz drobniejszymi oczkami" + Etap 1: sito o dużych oczkach → odpada piach (oczywiste nie-twarze) + Etap 25: sito najdrobniejsze → zostaje ZŁOTO (twarz) + 99% okien odpada w pierwszych 3 etapach → REAL-TIME! + +**Pełny pipeline Viola-Jones:** + + 1. Sliding window (24×24) po obrazie w wielu skalach + 2. Integral Image (preprocessing, O(n) — raz) + 3. Dla każdego okna: kaskada (Haar + AdaBoost, najczęściej odrzuci w 1-3 etapie) + 4. NMS na detekcjach → wynik + + Mnemonik pipeline'u: „SIKN" = Sliding → Integral → Kaskada → NMS + = „Szybko Identyfikuj Kształty Niezwykłe!" --- @@ -5843,6 +6647,8 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc Fast R-CNN: [CNN raz] → [ROI Pool 2000 regionów] → [FC] = 2s lepiej Faster R-CNN:[CNN] → [RPN generuje propozycje] → [ROI Pool] → [FC] = 0.2s! +![Ewolucja detektorów: R-CNN → Faster R-CNN](img/q24_rcnn_evolution.png) + **One-stage detectors (jednoetapowe)** — klasyfikacja i lokalizacja w JEDNYM przejściu. | Model | Rok | Szybkość | Innowacja | @@ -5860,13 +6666,7 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc **Two-stage vs One-stage:** - Cecha Two-stage (Faster R-CNN) One-stage (YOLO) - ───────────────────────────────────────────────────────────────── - Szybkość ~5 fps 45-155 fps - Dokładność (mAP) wyższa (historycznie) dorównuje (YOLOv8) - Małe obiekty lepszy gorszy (ale SSD/FPN pomaga) - Architektura 2 etapy + NMS 1 etap + NMS (DETR: bez NMS) - Real-time? nie TAK +![Two-stage vs One-stage — porównanie](img/q24_two_vs_one_stage.png) --- @@ -5874,59 +6674,236 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak go użyć do **lokalizacji** obiektów? -**Podejście 1 — Sliding Window (najwolniejsze):** +**Mnemonik 3 podejść: „SRF" = „Sliding → Region → Fine-tune" = „Szukaj Ręcznie, Finalnie optymalizuj!"** - Wytnij okno → klasyfikuj → przesuń → powtórz → NMS - Obraz 640×480, okno 64×64, krok 8px, 5 skal: - ~240 000 pozycji × 5 skal = ~1 200 000 klasyfikacji! - Przy 100 cls/sec → 3.3 godziny na 1 obraz → NIEPRAKTYCZNE +![Jak zbudować detektor z klasyfikatora? — 3 podejścia](img/q24_detector_from_classifier.png) -**Podejście 2 — Region Proposals + Klasyfikator (szybsze):** +--- - Selective Search → ~2000 regionów (zamiast milionów) - Każdy region → resize → klasyfikator → wynik + NMS - Przy 100 cls/sec → 20 sec/obraz → lepiej, ale wciąż wolno - To jest dokładnie R-CNN (2014) +#### Podejście 1 — Sliding Window (najprostsze, NAJWOLNIEJSZE) -**Podejście 3 — Fine-tune backbone + detection head (najlepsze):** +**Idea:** Wycinaj prostokątne fragmenty obrazu, KAŻDY pokaż klasyfikatorowi, zbierz pozytywne. - Pretrained classifier (ResNet): obraz → cechy → FC → "kot" - Zamień FC na detection head: - obraz → cechy (backbone) → [cls head: P(klasa)] - → [bbox head: Δx, Δy, Δw, Δh] - Dotrenuj na danych z bounding boxami (COCO, VOC) - = Transfer learning → NAJLEPSZA jakość + szybkość - To jest Faster R-CNN, YOLO, SSD — wszystkie używają pretrained backbone! +**Mnemonik: „WYCINAJ i PYTAJ" — jak wycinanie ciasteczek: koło po kole, aż cały obraz pokryty.** - Podsumowanie: - Sliding Window: ~milion klasyfikacji → NIEPRAKTYCZNE - Region Proposals: ~2000 klasyfikacji → wolne ale działa (R-CNN) - Fine-tune: 1 przejście sieci → szybkie i dokładne (Faster R-CNN, YOLO) +![Sliding Window — najprostsze podejście](img/q24_sliding_window.png) + + Pseudokod: + def sliding_window_detect(image, classifier, window_size=64, step=8): + detections = [] + for scale in [0.5, 0.75, 1.0, 1.5, 2.0]: # 5 skal + resized = resize(image, scale) + for y in range(0, resized.height - window_size, step): + for x in range(0, resized.width - window_size, step): + window = resized[y:y+window_size, x:x+window_size] + label, confidence = classifier.predict(window) + if label != "tło" and confidence > 0.5: + # przelicz współrzędne na oryginał + bbox = (x/scale, y/scale, + (x+window_size)/scale, (y+window_size)/scale) + detections.append((label, bbox, confidence)) + return nms(detections) # usuń duplikaty + +**Dlaczego wiele skal?** Obiekty mają różne rozmiary — kot blisko = duży, kot daleko = mały. Okno 64×64 nie złapie kota 200×200. + + Obliczenia dla obrazu 640×480: + Pozycje na skali 1.0: (640-64)/8 × (480-64)/8 = 72 × 52 = 3 744 + × 5 skal = 18 720 okien + × klasyfikacja ResNet (~10ms/obraz na GPU) = ~3 minuty + × na CPU (~100ms/obraz) = ~30 minut na 1 obraz! + ⚠ NIEPRAKTYCZNE dla zastosowań real-time + +**Wady:** (1) Ekstremalnie wolne. (2) Stały kształt okna — obiekty nie są kwadratowe. (3) ~99.9% okien to „tło" → marnowanie czasu. + +--- + +#### Podejście 2 — Region Proposals + Klasyfikator (= R-CNN) + +**Idea:** Zamiast milionów okien, inteligentnie zaproponuj ~2000 regionów, w których MOGĄ być obiekty, i tylko te sklasyfikuj. + +**Mnemonik: „INTELIGENTNE CIĘCIE" — zamiast kroić cały tort na milion kawałków, wytnij tylko tam, gdzie widzisz wiśnie (obiekty).** + + Pseudokod (= R-CNN): + def region_proposal_detect(image, classifier): + # Krok 1: Selective Search — inteligentnie generuj regiony + proposals = selective_search(image) # ~2000 prostokątów + detections = [] + + # Krok 2: Dla KAŻDEGO regionu — clasificuj + for bbox in proposals: # ~2000 iteracji (nie milion!) + crop = image[bbox] # wytnij region + crop = resize(crop, 224, 224) # rozmiar wymagany przez CNN + features = cnn_backbone(crop) # ResNet → wektor 2048 cech + label, conf = svm_classify(features) # SVM: "samochód? kot? tło?" + if label != "tło" and conf > 0.5: + detections.append((label, bbox, conf)) + + # Krok 3: bbox regression — doprecyzuj pozycje + for det in detections: + det.bbox += bbox_regressor(det.features) # Δx, Δy, Δw, Δh + + return nms(detections) # Krok 4: usuń duplikaty + +**Dlaczego 2000 a nie milion?** Selective Search łączy podobne fragmenty obrazu (kolor, tekstura) bottom-up. Wynik: ~2000 „mądrych" propozycji, z których ~50% zawiera coś (vs 0.1% w sliding window). + + Porównanie z sliding window: + Sliding Window: ~18 000 okien × 10ms = ~3 min + Proposals: ~2 000 regionów × 10ms = ~20 sec ← 9× szybciej + ALE wciąż 2000 × forward pass CNN → dlatego powstał Fast R-CNN! + +**Wady:** (1) Selective Search jest osobnym algorytmem (nie end-to-end). (2) 2000 × forward pass CNN = wciąż wolno. (3) SVM trenowany OSOBNO od CNN. + +--- + +#### Podejście 3 — Fine-tune backbone + detection head (NAJLEPSZE) + +**Idea:** Weź pretrenowany klasyfikator, ODETNIJ głowicę klasyfikacyjną (FC 1000 klas), zastąp ją DWOMA nowymi głowicami: (1) głowica klasyfikacji → klasa obiektu, (2) głowica regresji → pozycja bbox. + +**Mnemonik: „PRZESZCZEP GŁOWY" — ten sam silnik (backbone), nowa głowa (detection head).** + + Pseudokod (= Faster R-CNN / YOLO w uproszczeniu): + # KROK 1: Weź pretrenowany klasyfikator + resnet = load_pretrained("resnet50_imagenet") # 1000 klas ImageNet + + # KROK 2: Odetnij starą głowicę klasyfikacji + backbone = resnet.layers[:-2] # ZACHOWAJ: Conv1...Conv5 (ekstraktor cech) + # WYRZUĆ: FC(1000) + Softmax + + # KROK 3: Dodaj nowe głowice detekcji + class DetectionHead: + def __init__(self): + self.cls_head = Linear(2048, num_classes) # "samochód? kot? tło?" + self.bbox_head = Linear(2048, 4) # Δx, Δy, Δw, Δh + + def forward(self, features): + cls = softmax(self.cls_head(features)) # P(klasa) + bbox = self.bbox_head(features) # przesunięcie bbox + return cls, bbox + + # KROK 4: Zamroź backbone, trenuj głowice na danych detekcyjnych + for image, gt_boxes, gt_labels in coco_dataset: + features = backbone(image) # pretrenowane cechy (zamrożone) + cls, bbox = detection_head(features) + loss = cls_loss(cls, gt_labels) + bbox_loss(bbox, gt_boxes) + loss.backward() # aktualizuj TYLKO detection_head + + # KROK 5 (opcja): Fine-tune — odmroź backbone z MAŁYM learning rate + backbone.unfreeze() + optimizer = SGD(lr=0.0001) # 10× mniejszy niż dla głowicy! + # trenuj jak w kroku 4, ale teraz backbone też się uczy + +**Dlaczego to działa?** Pretrenowany backbone na ImageNet „wie", jak wyglądają krawędzie, tekstury, kształty. Te cechy są UNIWERSALNE — przydają się zarówno do klasyfikacji „złota rybka vs samolot" jak i do detekcji „samochód na zdjęciu z drona". + + Transfer learning w liczbach: + Trenowanie od zera na COCO (330K obrazów): ~12h na 8×V100 GPU + Fine-tune pretrained ResNet-50: ~4h na 8×V100 GPU ← 3× szybciej! + Fine-tune osiąga mAP ~42%, od zera ~38% ← lepsze wyniki! + +**Pełny przykład w PyTorch (Faster R-CNN z pretrained backbone):** + + import torchvision + from torchvision.models.detection import fasterrcnn_resnet50_fpn + + # Gotowy detektor z pretrained backbone! + model = fasterrcnn_resnet50_fpn(pretrained=True) + + # Custom: zmiana na 5 klas (zamiast 91 COCO) + num_classes = 5 # 4 obiekty + tło + in_features = model.roi_heads.box_predictor.cls_score.in_features + model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) + + # Trening: + model.train() + for images, targets in dataloader: + loss_dict = model(images, targets) # cls_loss + bbox_loss + total_loss = sum(loss_dict.values()) + total_loss.backward() + optimizer.step() + + # Inferencja: + model.eval() + predictions = model([test_image]) + # predictions = [{'boxes': tensor, 'labels': tensor, 'scores': tensor}] + # boxes = [[x1,y1,x2,y2], ...], labels = [1, 3, ...], scores = [0.95, 0.88, ...] + +--- + +#### Podsumowanie — porządek od NAJGORSZEGO do NAJLEPSZEGO: + + Podejście Okien Czas/obraz Jakość Rok Przykład + ────────────────────────────────────────────────────────────────────────── + Sliding Window ~milion ~30 min niska - (teoria) + Region Proposals ~2000 ~20-50 sec średnia 2014 R-CNN + Fine-tune + RPN ~300 ~0.2 sec wysoka 2015 Faster R-CNN + One-stage 1×siatka ~7-22 ms wysoka 2016+ YOLO, SSD + Transformer N queries ~25 ms wysoka 2020 DETR + + Mnemonik porządku: „SRFTD" = „Sliding → Region → Fine-tune → Transformer → (Done!)" + = „Szukaj Ręcznie, Finalnie Transformer (Detekuje!)" --- ### NMS (Non-Maximum Suppression) — post-processing +![NMS — usuwanie duplikatów](img/q24_nms_steps.png) + Detektor generuje WIELE nakładających się bbox dla jednego obiektu: [bbox1, 0.95], [bbox2, 0.90], [bbox3, 0.85] — wszystkie na tym samym kocie - Algorytm NMS: - 1. Sortuj po confidence: [0.95, 0.90, 0.85] - 2. Weź najlepszą (0.95) → ZACHOWAJ - 3. Oblicz IoU z resztą: IoU(bbox1,bbox2)=0.82, IoU(bbox1,bbox3)=0.75 - 4. Usuń te z IoU > próg (0.5): usuń bbox2 i bbox3 - 5. Powtórz dla następnej najlepszej - Wynik: 1 bbox per obiekt + Pseudokod NMS: + def nms(detections, iou_threshold=0.5): + detections.sort(by=confidence, descending=True) + keep = [] + while detections: + best = detections.pop(0) # weź najlepszą + keep.append(best) # ZACHOWAJ + detections = [d for d in detections + if iou(best, d) < iou_threshold] # usuń nakładające + return keep + + Krok po kroku (przykład): + 1. Sortuj: [0.95, 0.90, 0.85, 0.40] + 2. Weź bbox₁ (0.95) → ZACHOWAJ + 3. IoU(bbox₁, bbox₂) = 0.82 > 0.5 → USUŃ (duplikat!) + IoU(bbox₁, bbox₃) = 0.75 > 0.5 → USUŃ (duplikat!) + IoU(bbox₁, bbox₄) = 0.10 < 0.5 → ZACHOWAJ (INNY obiekt!) + 4. Wynik: [bbox₁, bbox₄] — 2 unikalne obiekty + +![IoU (Intersection over Union)](img/q24_iou_diagram.png) + + Mnemonik NMS: „Najlepszy Ma Się dobrze" — zachowaj najlepszą, resztę wyrzuć + Mnemonik IoU: „Ile pokrycia Ustalono?" — pole(∩) / pole(A∪B) ### Etymologia -**YOLO** — You Only Look Once (Joseph Redmon et al., 2016). **R-CNN** — Region-based CNN (Ross Girshick, 2014). **HOG** — Histogram of Oriented Gradients (Dalal & Triggs, 2005). **SVM** — Support Vector Machine (Vapnik, 1995). **Viola-Jones** — Paul Viola + Michael Jones (2001). **DETR** — DEtection TRansformer (Facebook AI, 2020). **SSD** — Single Shot MultiBox Detector (Liu et al., 2016). **NMS** — Non-Maximum Suppression; tłumienie nie-maksymalnych detekcji. +**CNN** — Convolutional Neural Network (sieć z konwolucjami). **YOLO** — You Only Look Once (Joseph Redmon et al., 2016). **R-CNN** — Region-based CNN (Ross Girshick, 2014). **HOG** — Histogram of Oriented Gradients (Dalal & Triggs, 2005). **SVM** — Support Vector Machine (Vapnik, 1995). **Viola-Jones** — Paul Viola + Michael Jones (2001). **DETR** — DEtection TRansformer (Facebook AI, 2020). **SSD** — Single Shot MultiBox Detector (Liu et al., 2016). **NMS** — Non-Maximum Suppression; tłumienie nie-maksymalnych detekcji. **ROI** — Region of Interest (region zainteresowania). **RPN** — Region Proposal Network (sieć propozycji regionów). **FPN** — Feature Pyramid Network (piramida cech). **IoU** — Intersection over Union (przecięcie przez sumę). **FC** — Fully Connected (w pełni połączona). **ReLU** — Rectified Linear Unit (wyprostowana jednostka liniowa). **mAP** — mean Average Precision (średnia precyzja). ### Jak zapamiętać -- **YOLO = „You Only Look Once"** — jednoetapowy, szybki +- **CNN = „Czytaj Nie Naraz"** — małe filtry 3×3 przesuwane po obrazie, nie cały obraz naraz +- **Hierarchia CNN: „K-R-F-O" = „Każdy Rycerz Znajduje Obiekt"** — Krawędzie → Rogi → Fragmenty → Obiekty +- **FC = „Full Connection"** — każdy z każdym, warstwa decyzyjna na końcu CNN +- **Backbone = SILNIK samochodu** — ten sam silnik (ResNet), różne karoserie (klasyfikacja/detekcja/segmentacja) +- **Backbone'y: A→V→R = „Architektura Bardzo Rezylientna"** — AlexNet (2012) → VGG (2014) → ResNet (2015) +- **Transfer learning = „PRZESZCZEP GŁOWY"** — nie ucz się od zera, przenieś wiedzę z ImageNet, zmień głowicę +- **HOG kroki: „GOKBN" = „Grasz Ostro, Kumplu? Bądź Naturalny"** — Gradienty → Orientacja → Komórki → Bloki → Normalizacja +- **SVM = „LINIA MAKSYMALNEGO ODDECHU"** — margines jak most: im szerszy, tym bezpieczniej +- **Viola-Jones: „HIC" = Haar + Integral Image + Cascade** +- **Haar = „Hej, A tu jest Różnica?"** — porównuje jasne i ciemne prostokąty +- **Integral Image = „4 Odczyty I Gotowe" (4OIG)** — suma dowolnego prostokąta O(1) +- **Kaskada = „SITO"** — piach odpada wcześnie, złoto (twarz) zostaje na końcu +- **Viola-Jones pipeline: „SIKN" = „Szybko Identyfikuj Kształty Niezwykłe"** — Sliding → Integral → Kaskada → NMS +- **AdaBoost = „ADAptacyjnie BOOSTuj"** — słabe modele razem = silny +- **Selective Search** — inteligentne łączenie regionów zamiast milionów okien +- **ROI Pooling** — dowolny rozmiar → stały rozmiar (siatkowanie + max) +- **Bbox regression = „GPS korekta"** — popraw przybliżoną pozycję o Δx, Δy, Δw, Δh +- **Ewolucja R-CNN: „CORAZ MNIEJ MARNOWANIA"** — R-CNN (50s) → Fast (2s) → Faster (0.2s) +- **YOLO = „You Only Look Once"** — jednoetapowy, szybki, siatka S×S - **Faster R-CNN = CNN + RPN + ROI Pool** — dwuetapowy, dokładny -- **Detektor z klasyfikatora:** sliding window (wolno) → proposals (lepiej) → fine-tune backbone (najlepiej) +- **NMS = „Najlepszy Ma Się dobrze"** — zachowaj najlepszą detekcję, usuń duplikaty +- **IoU = „Ile pokrycia Ustalono?"** — pole(∩) / pole(A∪B) +- **DETR = „Detekcja Eliminująca Trikowe Redundancje"** — bez NMS, bez anchorów, transformer +- **Detektor z klasyfikatora: „SRF" = „Szukaj Ręcznie, Finalnie optymalizuj!"** — Sliding Window (wolno) → Region Proposals (lepiej) → Fine-tune backbone (najlepiej) \newpage @@ -6489,15 +7466,16 @@ Nadawca kopiuje dane do wcześniej zaalokowanego bufora i wraca natychmiast. Rec │ → NIE → wróć do 1│ └──────────────────────────────────────────┘ -**Metody interaktywne (interactive methods)** — konkretne algorytmy realizujące interaktywne wspomaganie decyzji. W kontekście tego pytania są to: metoda loterii (wyznaczanie funkcji użyteczności U(x) przez pytania o loterie), metoda certainty equivalent (wyznaczanie ekwiwalentu pewności), AHP (porównania parami), PROMETHEE i ELECTRE (metody outranking). Każda z nich wymaga od decydenta ODPOWIEDZI na pytania — to czyni je interaktywnymi. +**Metody interaktywne (interactive methods)** — konkretne algorytmy realizujące interaktywne wspomaganie decyzji. W kontekście tego pytania są to KRYTERIA DECYZYJNE stosowane gdy decydent nie zna prawdopodobieństw stanów natury (lub je zakłada). Interaktywność polega na tym, że decydent WYBIERA kryterium (a w przypadku Hurwicza — także parametr α), co wymaga dialogu o jego postawie wobec ryzyka. - Metoda Jakie pytania zadaje decydentowi? + Kryterium Pytanie do decydenta / założenie ────────────────────────────────────────────────────────────────── - Loteria „Wolisz X na pewno, czy loterię (p: best, 1-p: worst)?" - CE „Ile na pewno = ta loteria?" - AHP „Ile razy kryterium A ważniejsze od B?" (skala 1-9) - PROMETHEE „Jak ważne jest każde kryterium?" (wagi) - ELECTRE „Jaki próg zgody/sprzeciwu?" + Wart. oczekiwana „Znasz prawdopodobieństwa stanów?" (potrzebne p) + Laplace'a „Każdy stan natury równie prawdopodobny" (założenie) + Optymistyczne „Zawsze liczysz na najlepszy scenariusz?" (postawa) + Pesymistyczne „Chcesz zabezpieczyć się przed najgorszym?" (postawa) + Hurwicza „Podaj swój współczynnik optymizmu α ∈ [0,1]" (parametr) + Savage'a „Chcesz minimalizować żal z podjętej decyzji?" (postawa) --- @@ -6509,101 +7487,234 @@ Nadawca kopiuje dane do wcześniej zaalokowanego bufora i wraca natychmiast. Rec Przykład ryzyka: „Z 60% szansą zysk 100 zł, z 40% strata 50 zł." Przykład niepewności: „Możemy zyskać lub stracić, ale nie wiemy ile i z jakim prawdopodobieństwem." ---- - -**Decydent (decision maker)** — osoba lub podmiot, który musi wybrać jedną z dostępnych alternatyw. Metody interaktywne wymagają dialogu z decydentem — pytamy go o preferencje, zamiast zakładać je z góry. - -**Funkcja użyteczności U(x) (utility function)** — matematyczne przypisanie „wartości subiektywnej" do wyniku. Dla kogoś, kto boi się ryzyka, różnica między 0 a 1000 zł jest bardziej odczuwalna niż między 9000 a 10000 zł. - - U(x) - │ ╭──────── wklęsła (risk-averse) - │ ╱╱ - │ ╱╱ - │╱╱ - └──────────── x (pieniądze) - -**Risk averse (awersja do ryzyka)** — decydent preferuje pewne wyniki nad ryzykowne loterie o tej samej wartości oczekiwanej. Funkcja U jest **wklęsła** (concave): U''(x) < 0. - - Loteria: 50% szans na 0 zł, 50% na 100 zł → E[X] = 50 zł - Risk-averse: „Wolę 50 zł na pewno" (a nawet 40 zł na pewno!) - -**Risk neutral (neutralność)** — U jest liniowa. Decydentowi jest obojętne czy dostanie E[X] na pewno, czy zagra w loterię. - -**Risk seeking (skłonność do ryzyka)** — U jest **wypukła** (convex). Decydent woli ryzyko niż pewny E[X]. „Wolę zagrać niż dostać pewniaka." +![Warunki decyzyjne — spektrum wiedzy decydenta](img/q31_conditions_spectrum.png) --- -**Loteria (lottery)** — formalizacja decyzji ryzykownej: zbiór wyników z ich prawdopodobieństwami. Notacja L = (p: best, 1-p: worst). +**Decydent (decision maker)** — osoba lub podmiot, który musi wybrać jedną z dostępnych alternatyw. Metody interaktywne wymagają dialogu z decydentem — pytamy go o postawę wobec ryzyka (optymista? pesymista?) i ew. parametry (α Hurwicza). - L = (0.6: 100 zł, 0.4: 0 zł) - E[L] = 0.6 × 100 + 0.4 × 0 = 60 zł +**Stan natury (state of nature)** — scenariusz/sytuacja zewnętrzna, na którą decydent NIE ma wpływu. Np. pogoda, koniunktura gospodarcza, zachowanie konkurencji. Oznaczamy S₁, S₂, …, Sₙ. -**Metoda loterii (lottery method)** — technika wyznaczania U(x) przez zadawanie pytań decydentowi. Ustalamy U(worst)=0, U(best)=1 i szukamy „indifference point" — prawdopodobieństwa p*, przy którym decydent jest obojętny między pewną kwotą a loterią. + Stany natury: S₁ = „dobra koniunktura", S₂ = „zła koniunktura" + Decydent NIE wybiera stanu — stan „się zdarza". - Pyt: „Wolisz 500 zł na pewno, czy loterię (p: 1000 zł, 1-p: 0 zł)?" - Jeśli punkt obojętności p* = 0.7 → U(500) = 0.7 - (Risk-neutral dałby p*=0.5, bo 500/1000=0.5) +**Macierz wypłat (payoff matrix)** — tabela, w której wiersze = alternatywy (decyzje), kolumny = stany natury, a komórki = wyniki (wypłaty). To podstawowa struktura danych dla WSZYSTKICH kryteriów decyzyjnych. -**Certainty Equivalent (CE, ekwiwalent pewności)** — pewna kwota, która jest dla decydenta równoważna danej loterii. + Przykład — macierz wypłat (zyski w tys. zł): + S₁ (dobra) S₂ (średnia) S₃ (zła) + ───────────────────────────────────────────────────── + A₁ (fabryka) 200 50 −100 + A₂ (sklep) 80 70 40 + A₃ (obligacje) 30 30 30 - Loteria: 50/50 zysk 100 zł lub 0 zł → E[X] = 50 zł - Decydent risk-averse: CE = 35 zł (wolałby 35 zł na pewno niż grać) - Risk premium = E[X] − CE = 50 − 35 = 15 zł + A₁ może dać 200k, ale też stratę 100k. + A₃ daje 30k niezależnie od stanu → decyzja bezpieczna. -**Wartość oczekiwana E[X] (expected value)** — średni wynik loterii ważony prawdopodobieństwami. +**Wartość oczekiwana E[X] (expected value)** — średni wynik ważony prawdopodobieństwami stanów natury. Używana w kryterium wartości oczekiwanej (gdy znamy prawdopodobieństwa) i w kryterium Laplace'a (z równymi prawdopodobieństwami). E[X] = Σ pᵢ × xᵢ - Dla L = (0.3: 100, 0.7: 20): E[X] = 0.3×100 + 0.7×20 = 44 + + Przykład z PRAWDZIWYMI prawdopodobieństwami (p₁=0.5, p₂=0.3, p₃=0.2): + E[A₁] = 0.5×200 + 0.3×50 + 0.2×(−100) = 100 + 15 − 20 = 95 ← MAX + E[A₂] = 0.5×80 + 0.3×70 + 0.2×40 = 40 + 21 + 8 = 69 + E[A₃] = 0.5×30 + 0.3×30 + 0.2×30 = 15 + 9 + 6 = 30 + + Dla Laplace'a (równe prawdopodobieństwa, p₁ = p₂ = p₃ = 1/3): + E[A₁] = (200 + 50 + (−100)) / 3 = 150/3 = 50 + E[A₂] = (80 + 70 + 40) / 3 = 190/3 ≈ 63.3 ← najlepsza wg Laplace'a + E[A₃] = (30 + 30 + 30) / 3 = 30 + +**Kryterium wartości oczekiwanej (expected value criterion)** — NAJPROSTSZA metoda decyzyjna W WARUNKACH RYZYKA (gdy znamy prawdopodobieństwa). Oblicz E[Aᵢ] = Σⱼ pⱼ × aᵢⱼ dla każdej alternatywy i wybierz tę z NAJWYŻSZĄ wartością oczekiwaną. + + Formuła: V(Aᵢ) = Σⱼ pⱼ × aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + Przykład (p₁=0.5, p₂=0.3, p₃=0.2): + V(A₁) = 0.5×200 + 0.3×50 + 0.2×(−100) = 95 ← MAX → wybieramy A₁ + V(A₂) = 0.5×80 + 0.3×70 + 0.2×40 = 69 + V(A₃) = 0.5×30 + 0.3×30 + 0.2×30 = 30 + + Kluczowa różnica od Laplace'a: + - Laplace: ZAKŁADA p = 1/n (bo nie znamy prawdopodobieństw) + - Wart. oczekiwana: UŻYWA PRAWDZIWYCH p (bo je znamy!) + + Przykład życiowy: firma rozważa inwestycję + - Analityk oszacował: P(boom) = 50%, P(stabilna) = 30%, P(kryzys) = 20% + - Fabryka wygrywa (E=95k), bo wysoki zysk w boomie (200k) × duże p (50%) + przeważa nad stratą w kryzysie (−100k) × małe p (20%) + + Ograniczenie: E[X] ignoruje ROZRZUT wyników! A₁ ma E=95k, ale może + dać −100k. Decydent z awersją do ryzyka może wolę A₂ (E=69k, ale + minimum 40k). Dlatego sam E[X] nie wystarczy — potrzeba też analizy + ryzyka (np. wariancji, worst-case). + + Mnemonik: „Średnia ważona — jak średnia ocen" + Wynik × prawdopodobieństwo = waga. + Sumuj wagi → E[X]. Jak w dzienniku: 5×0.3 + 4×0.5 + 2×0.2 = 3.9 + +![Kryterium wartości oczekiwanej — rozkład wyników](img/q31_expected_value.png) --- -**AHP (Analytic Hierarchy Process)** — metoda Saaty'ego do wyboru najlepszej alternatywy gdy mamy wiele kryteriów. Rozbija problem na hierarchię: Cel → Kryteria → Alternatywy. +**Kryterium decyzyjne (decision criterion)** — reguła/algorytm, który z macierzy wypłat wyznacza „najlepszą" alternatywę. Każde kryterium odzwierciedla INNĄ postawę decydenta wobec ryzyka. Dlatego to samo zadanie może dać INNE odpowiedzi zależnie od wybranego kryterium — i to jest OK. - Cel: Wybierz samochód - ├── Kryterium: Cena - │ ├── Auto A, Auto B, Auto C - ├── Kryterium: Komfort - │ ├── Auto A, Auto B, Auto C - └── Kryterium: Spalanie - ├── Auto A, Auto B, Auto C + Te same dane, różne kryteria → różne „najlepsze" decyzje: + Kryterium Wygrywa Dlaczego? + ───────────────────────────────────────────────────── + Wart. oczekiwana A₁ (95) najwyższa E[X] z prawdziwymi p + Laplace A₂ (≈63) najwyższa średnia (równe p) + Optymistyczne A₁ (200) najwyższy max + Pesymistyczne A₂ (40) najwyższy min (bezpieczne) + Hurwicz (α=0.6) A₁ (80) kompromis + Savage A₂ (120) najniższy max żalu -**Porównania parami (pairwise comparisons)** — w AHP porównujemy każdą parę kryteriów/alternatyw i oceniamy na skali 1-9 Saaty'ego: +![Porównanie kryteriów — macierz wypłat i wykresy](img/q31_criteria_comparison.png) - 1 = równe znaczenie - 3 = umiarkowana przewaga - 5 = silna przewaga - 7 = bardzo silna - 9 = absolutna przewaga +**Kryterium Laplace'a (Laplace criterion / principle of insufficient reason)** — zakładamy, że WSZYSTKIE stany natury są RÓWNIE PRAWDOPODOBNE (bo nie mamy powodu faworyzować żadnego). Obliczamy średnią arytmetyczną wypłat dla każdej alternatywy i wybieramy najwyższą. - Macierz 3×3 (Cena vs Komfort vs Spalanie): - Cena Komf Spal - Cena [ 1 3 5 ] - Komf [ 1/3 1 2 ] - Spal [ 1/5 1/2 1 ] + Formuła: V(Aᵢ) = (1/n) × Σⱼ aᵢⱼ (n = liczba stanów natury) -**Eigenvalue (wartość własna)** — z macierzy porównań wyznaczamy wektor własny → wagi kryteriów. To serce AHP: macierz parami → ranking numeryczny. + Przykład z macierzy powyżej (n=3): + V(A₁) = (200 + 50 + (−100)) / 3 = 50.0 + V(A₂) = (80 + 70 + 40) / 3 = 63.3 ← MAX → wybieramy A₂ + V(A₃) = (30 + 30 + 30) / 3 = 30.0 -**Consistency Ratio (CR)** — miara spójności ocen decydenta. Jeśli A>B i B>C, ale C>A, to niespójne. CR < 0.1 = akceptowalne. CR ≥ 0.1 → decydent powinien poprawić oceny. + Interaktywność: decydent musi zaakceptować założenie równych + prawdopodobieństw — „Czy zgadzasz się, że każdy scenariusz + jest tak samo możliwy?" + + Przykład życiowy: wybieram restaurację w nieznanym mieście. + Nie wiem, która dobra — traktuję je „po równo" i porównuję + średnią ocen z 3 portali (każdy portal = stan natury z p=1/3). + + Mnemonik: „Laplace = Loteria — Losowe, ALE Po równo" + +**Kryterium optymistyczne (maximax / optimistic criterion)** — decydent-OPTYMISTA: dla każdej alternatywy bierzemy NAJLEPSZY możliwy wynik (max w wierszu), potem wybieramy alternatywę z najwyższym z tych maksimów. + + Formuła: V(Aᵢ) = maxⱼ aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + max(A₁) = max(200, 50, −100) = 200 ← MAX → wybieramy A₁ + max(A₂) = max(80, 70, 40) = 80 + max(A₃) = max(30, 30, 30) = 30 + + A₁ wygrywa — optymista liczy na najlepszy scenariusz (200k). + Ryzyko: jeśli S₃, to strata −100k! + + Przykład życiowy: gracz w pokera, który zawsze idzie all-in, + bo „może trafię straight flush". Patrzy TYLKO na najlepsze + możliwe rozdanie. Ignoruje szansę przegranej. + + Mnemonik: „Maximax = Marzyciel — Max z Max, bo MARZĘ o najlepszym" + +**Kryterium pesymistyczne (maximin / Wald criterion)** — decydent-PESYMISTA: dla każdej alternatywy bierzemy NAJGORSZY możliwy wynik (min w wierszu), potem wybieramy alternatywę z najwyższym z tych minimów. Zabezpieczamy się przed najgorszym scenariuszem. + + Formuła: V(Aᵢ) = minⱼ aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + min(A₁) = min(200, 50, −100) = −100 + min(A₂) = min(80, 70, 40) = 40 + min(A₃) = min(30, 30, 30) = 30 + + max{−100, 40, 30} = 40 → wybieramy A₂ + Pesymista: „Nawet w najgorszym razie dostanę 40k" (A₂ jest bezpieczna). + + Przykład życiowy: jadąc na wakacje, pesymista wybiera hotel z gwarancją + zwrotu, bo „a jeśli będzie brzydka pogoda?". Woli gwarantowany minimum + komfort niż ryzykować. Ubezpieczenia działają na tej zasadzie. + + Mnemonik: „Maximin = Mur obronny — buduję MUR pod MINimum, bo zawsze + zakładam NAJGORSZE (Wald = Wall = Mur)" + +**Kryterium Hurwicza (Hurwicz criterion)** — kompromis między optymizmem a pesymizmem. Decydent podaje współczynnik optymizmu α ∈ [0, 1], gdzie α = 1 to pełny optymista, α = 0 to pełny pesymista. + + Formuła: V(Aᵢ) = α × maxⱼ aᵢⱼ + (1−α) × minⱼ aᵢⱼ + + Dla α = 0.6: + V(A₁) = 0.6×200 + 0.4×(−100) = 120 − 40 = 80 + V(A₂) = 0.6×80 + 0.4×40 = 48 + 16 = 64 + V(A₃) = 0.6×30 + 0.4×30 = 18 + 12 = 30 + + max{80, 64, 30} = 80 → A₁ wygrywa dla α=0.6. + + Dla α = 0.3 (bardziej pesymistyczny): + V(A₁) = 0.3×200 + 0.7×(−100) = 60 − 70 = −10 + V(A₂) = 0.3×80 + 0.7×40 = 24 + 28 = 52 ← teraz A₂! + V(A₃) = 0.3×30 + 0.7×30 = 9 + 21 = 30 + + → Zmiana α zmienia wynik! Dlatego TO kryterium jest najbardziej + interaktywne — decydent MUSI podać swoje α w dialogu. + + Przypadki specjalne: + α = 1 → kryterium optymistyczne (maximax) + α = 0 → kryterium pesymistyczne (maximin) + + Przykład życiowy: kupujesz akcje. Z α=0.8 (optymista) patrzysz głównie + na potencjalny zysk. Z α=0.2 (pesymista) prawie tylko na potencjalną + stratę. α to „pokrętło optymizmu" — kręcisz i widzisz jak zmienia + się rekomendacja. + + Mnemonik: „Hurwicz = Huśtawka — huśtasz się między max a min, + α mówi jak daleko w stronę max się wychylasz" + +![Kryterium Hurwicza — wpływ α na wybór](img/q31_hurwicz_alpha.png) + +**Współczynnik optymizmu α (optimism coefficient)** — parametr Hurwicza z przedziału [0, 1]. Wyraża postawę decydenta: α bliskie 1 = optymista (wierzy w dobre scenariusze), α bliskie 0 = pesymista. + + α = 1.0 → patrzę tylko na max → maximax + α = 0.5 → równa waga max i min + α = 0.0 → patrzę tylko na min → maximin --- -**PROMETHEE (Preference Ranking Organization METHod for Enrichment Evaluations)** — metoda porównująca alternatywy parami per kryterium za pomocą funkcji preferencji. Wynik: przepływy (flows). +**Macierz żalu / macierz strat (regret matrix)** — tabela, w której każda komórka zawiera ŻALE (regret) = ile TRACĘ wybierając daną alternatywę zamiast najlepszej w danym stanie natury. - Φ⁺(a) = outgoing flow = „o ile a jest lepsze od reszty" (siła) - Φ⁻(a) = incoming flow = „o ile reszta jest lepsza od a" (słabość) - Φ(a) = Φ⁺(a) − Φ⁻(a) = net flow → im wyższe, tym lepsza alternatywa + Obliczanie: rᵢⱼ = maxₖ aₖⱼ − aᵢⱼ (max w kolumnie minus wartość w komórce) -**ELECTRE (ÉLimination Et Choix Traduisant la REalité)** — metoda outranking: A przewyższa B (A S B) gdy: + Macierz wypłat: Macierz żalu: + S₁ S₂ S₃ S₁ S₂ S₃ max żalu + A₁ 200 50 −100 A₁ 0 20 140 140 + A₂ 80 70 40 A₂ 120 0 0 120 ← MIN + A₃ 30 30 30 A₃ 170 40 10 170 -1. **Concordance (zgoda):** wystarczająco dużo kryteriów popiera A nad B -2. **Discordance (sprzeciw):** żadne kryterium nie daje B drastycznej przewagi nad A + maxₖ aₖ₁ = 200, maxₖ aₖ₂ = 70, maxₖ aₖ₃ = 40 + r₁₁ = 200−200 = 0, r₁₂ = 70−50 = 20, r₁₃ = 40−(−100) = 140 + r₂₁ = 200−80 = 120, r₂₂ = 70−70 = 0, r₂₃ = 40−40 = 0 + r₃₁ = 200−30 = 170, r₃₂ = 70−30 = 40, r₃₃ = 40−30 = 10 - Cecha AHP PROMETHEE ELECTRE - ────────────────────────────────────────────────────────── - Input parami (skala) per-kryterium per-kryterium - Wynik wagi + ranking przepływy Φ relacja outranking - Typ kompensacyjna częściowo komp. niekompensacyjna - Sens wartość globalna przepływ netto eliminacja słabych +**Kryterium Savage'a (minimax regret / Savage criterion)** — minimalizacja MAKSYMALNEGO ŻALU. Dla każdej alternatywy znajdujemy największy żal (max w wierszu macierzy żalu), potem wybieramy alternatywę z NAJMNIEJSZYM max żalem. + + Formuła: V(Aᵢ) = maxⱼ rᵢⱼ → wybierz Aᵢ z min V(Aᵢ) + + max żalu(A₁) = max(0, 20, 140) = 140 + max żalu(A₂) = max(120, 0, 0) = 120 ← MIN → wybieramy A₂ + max żalu(A₃) = max(170, 40, 10) = 170 + + Interpretacja: „Niezależnie co się zdarzy, mój żal nie przekroczy 120k" + (gdybym wybrał A₁, mógłbym żałować aż 140k; A₃ → aż 170k). + + Przykład życiowy: wybieram studia. Po 5 latach zobaczę, jaki zawód + najlepiej zarabia. Żal = „ile bym zarobił na najlepszych studiach + minus ile zarabiam". Savage minimalizuje ten maksymalny żal — + wybieram studia, po których NIGDY nie będę żałować za bardzo. + + Mnemonik: „Savage = Szał żalu — Savage to dziki (savage) żal, + więc go minimalizuję. Min z max żalu = trzymam żal na smyczy." + +![Kryterium Savage'a — budowa macierzy żalu](img/q31_regret_matrix.png) + +--- + +**Porównanie kryteriów — tabela zbiorcza:** + + Kryterium Postawa Formuła Wymaga od decydenta + ────────────────────────────────────────────────────────────────────────────── + Wart. oczekiw. racjonalna Σ pⱼ·aᵢⱼ podanie prawdopodobieństw + Laplace neutralna średnia wypłat akceptacja równych p + Optymistyczne optymista max z max nic (automatyczne) + Pesymistyczne pesymista max z min nic (automatyczne) + Hurwicza kompromis α·max + (1−α)·min podanie α ∈ [0,1] + Savage'a minimalizacja min z max żalu nic (automatyczne) + żalu + +![Mapa mnemoniczna — wszystkie kryteria](img/q31_criteria_mnemonic.png) --- @@ -6611,31 +7722,32 @@ Przykład ryzyka: „Z 60% szansą zysk 100 zł, z 40% strata 50 zł." Przykład ### Interaktywność = dialog z decydentem → odkrycie preferencji (funkcji użyteczności) -### Metody +### Metody (kryteria decyzyjne) -**1. Metoda loterii:** Ustal U(worst)=0, U(best)=1. Pytaj: „Wolisz x_mid na pewno, czy loterię (p: best, 1-p: worst)?" Punkt obojętności p* = U(x_mid). +**0. Kryterium wartości oczekiwanej (E[X]):** WYMAGA prawdopodobieństw stanów (warunki RYZYKA). Oblicz E[Aᵢ] = Σⱼ pⱼ·aᵢⱼ. Wybierz max. Ograniczenie: ignoruje rozrzut/ryzyko. -**2. Certainty Equivalent (CE):** CE(L) = pewna kwota równoważna loterii L. -- CE < E[X] → risk averse (wklęsła U) -- CE = E[X] → risk neutral -- CE > E[X] → risk seeking -- Risk Premium = E[X] − CE +**1. Kryterium Laplace'a:** Załóż równe prawdopodobieństwa stanów (warunki NIEPEWNOŚCI). Oblicz średnią wypłat per alternatywa. Wybierz max średniej. Formuła: V(Aᵢ) = (1/n) × Σⱼ aᵢⱼ. -**3. AHP (Analytic Hierarchy Process):** Hierarchia: Cel → Kryteria → Alternatywy. Porównania parami (skala 1-9) → eigenvalue → wagi. Consistency Ratio CR < 0.1. +**2. Kryterium optymistyczne (maximax):** Dla każdej alternatywy weź max wypłatę. Wybierz alternatywę z max z tych max. Formuła: max maxⱼ aᵢⱼ. -**4. PROMETHEE:** Funkcje preferencji per kryterium; agregacja; przepływy Φ⁺, Φ⁻, Φ (net); ranking. +**3. Kryterium pesymistyczne (maximin / Walda):** Dla każdej alternatywy weź min wypłatę. Wybierz alternatywę z max z tych min. Formuła: max minⱼ aᵢⱼ. -**5. ELECTRE:** Concordance (zgoda) + Discordance (sprzeciw) → outranking aSb. +**4. Kryterium Hurwicza:** Kompromis: V(Aᵢ) = α × maxⱼ aᵢⱼ + (1−α) × minⱼ aᵢⱼ. Decydent podaje α ∈ [0,1]. α=1 → maximax, α=0 → maximin. + +**5. Kryterium Savage'a (minimax regret):** Zbuduj macierz żalu (rᵢⱼ = maxₖ aₖⱼ − aᵢⱼ). Dla każdej alternatywy weź max żal. Wybierz alternatywę z min max żalu. ### Etymologia -**AHP** — Thomas Saaty (U. of Pittsburgh, 1970s); Analytic Hierarchy Process. **PROMETHEE** — Preference Ranking Organization METHod for Enrichment Evaluations (Jean-Pierre Brans, 1982). **ELECTRE** — ÉLimination Et Choix Traduisant la REalité (Bernard Roy, 1965) = „Eliminacja i Wybór Odzwierciedlający Rzeczywistość". **Certainty Equivalent** — z teorii użyteczności von Neumanna-Morgensterna (1944). **Funkcja użyteczności** — Daniel Bernoulli (1738) wprowadził koncepcję; vN-M sformalizowali aksjomatycznie. +**Wartość oczekiwana** — pojęcie z XVII w., Blaise Pascal i Pierre de Fermat (1654), formalizacja hazardu; „ile przeciętnie wygrasz?". **Laplace** — Pierre-Simon de Laplace (1749–1827), francuski matematyk; zasada niedostatecznej racji (principle of insufficient reason) — jeśli nie mamy powodu faworyzować żadnego stanu, traktujemy je jako równie prawdopodobne. **Wald** — Abraham Wald (1902–1950), matematyk z Wiednia; kryterium maximin = strategia minimax z teorii gier. **Hurwicz** — Leonid Hurwicz (1917–2008), laureat Nobla z ekonomii 2007 (z Myersonem i Maskinem, za mechanism design); zaproponował kompromis z parametrem α. **Savage** — Leonard Jimmie Savage (1917–1971), amerykański statystyk; kryterium minimax regret — minimalizacja żalu (1951, „The Foundations of Statistics"). ### Jak zapamiętać -- **CE = „ile dałbyś za pewniaka zamiast loterii?"** → miara awersji do ryzyka -- **AHP = „porównaj parami, policz wagi"** (macierz → eigenvalue) -- **PROMETHEE = „przepływy"** (Φ⁺ outgoing, Φ⁻ incoming) +- **E[X] = „średnia ważona prawdopodobieństwami"** → jak średnia ocen w dzienniku, ale wagi to szanse +- **Laplace = „wszystko po równo"** → średnia arytmetyczna wypłat (Loteria — ALE Po równo) +- **Maximax = „marzyciel → max z max"** → najlepszy z najlepszych, ignoruje ryzyko +- **Maximin = „mur obronny → max z min"** → najlepszy z najgorszych (Wald = Wall = Mur) +- **Hurwicz = „huśtawka — α pomiędzy"** → α·max + (1−α)·min, kręcisz pokrętłem optymizmu +- **Savage = „szał żalu → min max żalu"** → macierz żalu → minimalizuj maksymalny żal (trzymaj żal na smyczy) \newpage diff --git a/pytania/generate_bf_negative_diagram.py b/pytania/generate_bf_negative_diagram.py new file mode 100644 index 0000000..e8d50b3 --- /dev/null +++ b/pytania/generate_bf_negative_diagram.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 +""" +Generate Bellman-Ford negative-weights & negative-cycle diagram for PYTANIE 2. + +Two-part figure: + Part 1: Graph with negative edge, Dijkstra WRONG vs Bellman-Ford CORRECT + Part 2: Negative cycle detection (add C→B(−3)) + +A4-compatible, monochrome-friendly, 300 DPI. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 10 +FS_SMALL = 6.5 +FS_EDGE = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +LIGHT_GREEN = '#D5E8D4' +LIGHT_RED = '#F8D7DA' +LIGHT_YELLOW = '#FFF9C4' + +# --- Graph layout for negative-weight example --- +# S→A(2), A→C(3), S→B(5), B→A(−4) +NEG_POS = {'S': (0.8, 2), 'A': (3.3, 3.2), 'B': (3.3, 0.8), 'C': (5.8, 2)} +NEG_EDGES = [('S', 'A', 2), ('A', 'C', 3), ('S', 'B', 5), ('B', 'A', -4)] + + +def draw_node(ax, name, pos, color='white', current=False, visited=False, + dist_label=None, fontsize=12, error=False): + x, y = pos + r = 0.35 + lw = 2.5 if current else 1.5 + ec = '#D32F2F' if current else ('#D32F2F' if error else LN) + fc = LIGHT_YELLOW if current else (LIGHT_GREEN if visited else color) + if error: + fc = LIGHT_RED + + circle = plt.Circle((x, y), r, fill=True, facecolor=fc, + edgecolor=ec, linewidth=lw, zorder=5) + ax.add_patch(circle) + ax.text(x, y, name, ha='center', va='center', fontsize=fontsize, + fontweight='bold', zorder=6) + + if dist_label is not None: + bbox_ec = '#D32F2F' if error else GRAY3 + bbox_fc = LIGHT_RED if error else 'white' + ax.text(x, y - 0.55, f'd={dist_label}', ha='center', va='center', + fontsize=FS, zorder=6, + bbox=dict(boxstyle='round,pad=0.15', facecolor=bbox_fc, + edgecolor=bbox_ec, alpha=0.95)) + + +def draw_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False, + negative=False, cycle_edge=False, offset=0.0): + x1, y1 = pos1 + x2, y2 = pos2 + + dx, dy = x2 - x1, y2 - y1 + length = np.sqrt(dx**2 + dy**2) + r = 0.38 + sx = x1 + r * dx / length + sy = y1 + r * dy / length + ex = x2 - r * dx / length + ey = y2 - r * dy / length + + # Offset perpendicular for parallel edges + if offset != 0: + perp_x = -dy / length * offset + perp_y = dx / length * offset + sx += perp_x; sy += perp_y + ex += perp_x; ey += perp_y + + if cycle_edge: + color = '#D32F2F' + lw = 2.5 + ls = '--' + elif negative: + color = '#D32F2F' + lw = 2.5 + ls = '-' + elif relaxed: + color = '#D32F2F' + lw = 2.5 + ls = '-' + elif highlighted: + color = '#1565C0' + lw = 2.0 + ls = '-' + else: + color = GRAY3 + lw = 1.5 + ls = '-' + + # Arrow + ax.annotate('', xy=(ex, ey), xytext=(sx, sy), + arrowprops=dict(arrowstyle='->', color=color, lw=lw, + linestyle=ls, shrinkA=0, shrinkB=0), + zorder=2) + + # Weight label + mx = (sx + ex) / 2 + my = (sy + ey) / 2 + perp_x = -dy / length * 0.22 + perp_y = dx / length * 0.22 + if offset != 0: + perp_x *= 0.5 + perp_y *= 0.5 + + weight_str = str(weight) + edge_fc = LIGHT_RED if negative or cycle_edge else 'white' + edge_ec = '#D32F2F' if negative or cycle_edge else GRAY3 + ax.text(mx + perp_x, my + perp_y, weight_str, ha='center', va='center', + fontsize=FS_EDGE, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.15', facecolor=edge_fc, + edgecolor=edge_ec, alpha=0.95), + zorder=4) + + +def draw_neg_graph(ax, edges, title="", dist=None, current=None, visited=None, + relaxed_edges=None, error_nodes=None, extra_edges=None, + node_positions=None): + if visited is None: + visited = set() + if relaxed_edges is None: + relaxed_edges = set() + if dist is None: + dist = {} + if error_nodes is None: + error_nodes = set() + if node_positions is None: + node_positions = NEG_POS + + ax.set_xlim(-0.5, 7.0) + ax.set_ylim(-0.8, 4.5) + ax.set_aspect('equal') + ax.axis('off') + if title: + ax.set_title(title, fontsize=FS, fontweight='bold', pad=5) + + all_edges = list(edges) + if extra_edges: + all_edges += extra_edges + + for u, v, w in all_edges: + rl = (u, v) in relaxed_edges + neg = w < 0 + cycle = extra_edges and (u, v, w) in extra_edges + # If B→A and A→B both exist, offset them + off = 0.0 + draw_edge(ax, node_positions[u], node_positions[v], w, + relaxed=rl, negative=neg, cycle_edge=cycle, offset=off) + + for name, pos in node_positions.items(): + is_current = (name == current) + is_visited = (name in visited) + d_label = dist.get(name, None) + is_error = (name in error_nodes) + draw_node(ax, name, pos, current=is_current, visited=is_visited, + dist_label=d_label, error=is_error) + + +def generate_bf_negative_weights(): + """ + Two-row figure: + Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT + Row 2: B-F iterations 1-3 step by step + """ + fig = plt.figure(figsize=(14, 10)) + fig.suptitle('Bellman-Ford — ujemne wagi vs Dijkstra\n' + 'Graf: S→A(2), A→C(3), S→B(5), B→A(−4). Start = S', + fontsize=FS_TITLE + 1, fontweight='bold', y=0.99) + + # ---- Row 1: Graph + Dijkstra wrong + BF correct ---- + + # Panel 1: The graph structure + ax1 = fig.add_subplot(2, 3, 1) + draw_neg_graph(ax1, NEG_EDGES, + title='Graf z ujemną wagą\n(B→A = −4, zaznaczona na czerwono)', + dist={'S': '0', 'A': '?', 'B': '?', 'C': '?'}) + # START label + ax1.annotate("START", xy=(NEG_POS['S'][0] - 0.35, NEG_POS['S'][1]), + xytext=(NEG_POS['S'][0] - 1.2, NEG_POS['S'][1]), + fontsize=FS, fontweight='bold', color='#D32F2F', + arrowprops=dict(arrowstyle='->', color='#D32F2F', lw=2), + va='center') + + # Panel 2: Dijkstra — WRONG + ax2 = fig.add_subplot(2, 3, 2) + draw_neg_graph(ax2, NEG_EDGES, + title='Dijkstra — BŁĘDNY wynik\nA zamknięty z d=2, nie poprawia przy B→A', + dist={'S': '0', 'A': '2', 'B': '5', 'C': '5'}, + visited={'S', 'A', 'B', 'C'}, + error_nodes={'A', 'C'}) + # Add "WRONG" annotations + ax2.text(NEG_POS['A'][0] + 0.6, NEG_POS['A'][1] + 0.3, '✗ powinno 1', + fontsize=FS_SMALL, color='#D32F2F', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_RED, + edgecolor='#D32F2F', alpha=0.9, lw=0.5)) + ax2.text(NEG_POS['C'][0] + 0.05, NEG_POS['C'][1] + 0.55, '✗ powinno 4', + fontsize=FS_SMALL, color='#D32F2F', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_RED, + edgecolor='#D32F2F', alpha=0.9, lw=0.5)) + + # Panel 3: Bellman-Ford — CORRECT + ax3 = fig.add_subplot(2, 3, 3) + draw_neg_graph(ax3, NEG_EDGES, + title='Bellman-Ford — POPRAWNY wynik\nUjemna waga B→A poprawnie propagowana', + dist={'S': '0', 'A': '1', 'B': '5', 'C': '4'}, + visited={'S', 'A', 'B', 'C'}, + relaxed_edges={('B', 'A')}) + ax3.text(NEG_POS['A'][0] + 0.6, NEG_POS['A'][1] + 0.3, '✓ poprawne!', + fontsize=FS_SMALL, color='#006400', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_GREEN, + edgecolor='#006400', alpha=0.9, lw=0.5)) + ax3.text(NEG_POS['C'][0] + 0.05, NEG_POS['C'][1] + 0.55, '✓ poprawne!', + fontsize=FS_SMALL, color='#006400', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_GREEN, + edgecolor='#006400', alpha=0.9, lw=0.5)) + + # ---- Row 2: B-F iterations step by step ---- + iterations = [ + { + 'title': 'B-F Iteracja 1\nRelaksuj WSZYSTKIE krawędzie', + 'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '5'}, + 'relaxed': {('S', 'A'), ('A', 'C'), ('S', 'B'), ('B', 'A')}, + 'detail': ('S→A: 0+2=2<∞ → A=2\n' + 'A→C: 2+3=5<∞ → C=5\n' + 'S→B: 0+5=5<∞ → B=5\n' + 'B→A: 5−4=1<2 → A=1 ✓'), + }, + { + 'title': 'B-F Iteracja 2\nPropagacja poprawionego A', + 'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '4'}, + 'relaxed': {('A', 'C')}, + 'detail': ('S→A: 0+2=2>1 ✗\n' + 'A→C: 1+3=4<5 → C=4 ✓\n' + 'S→B: 0+5=5=5 ✗\n' + 'B→A: 5−4=1=1 ✗'), + }, + { + 'title': 'B-F Iteracja 3\nBrak zmian → stabilne!', + 'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '4'}, + 'relaxed': set(), + 'detail': ('Wszystkie krawędzie:\n' + 'brak poprawy ✗\n' + '→ wynik stabilny\n' + '→ BRAK cyklu ujemnego'), + }, + ] + + for i, it in enumerate(iterations): + ax = fig.add_subplot(2, 3, i + 4) + draw_neg_graph(ax, NEG_EDGES, title=it['title'], + dist=it['dist'], + visited={'S', 'A', 'B', 'C'}, + relaxed_edges=it['relaxed']) + # Detail text below graph + ax.text(3.2, -0.5, it['detail'], ha='center', va='top', + fontsize=FS_SMALL, family='monospace', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, + edgecolor=GRAY3)) + + # Bottom note + fig.text(0.5, 0.01, + 'Dijkstra zamyka wierzchołki na stałe (zachłanność) → ujemna waga B→A(−4) nie może poprawić zamkniętego A.\n' + 'Bellman-Ford relaksuje WSZYSTKIE krawędzie w każdej iteracji → ujemne wagi propagują się poprawnie.', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW, edgecolor=LN)) + + plt.tight_layout(rect=[0, 0.05, 1, 0.95]) + plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_negative_weights.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ bellman_ford_negative_weights.png") + + +def generate_bf_negative_cycle(): + """ + Figure showing negative cycle detection. + Graph: S→A(2), A→C(3), S→B(5), B→A(−4), C→B(−3) [added edge] + Cycle: B→A→C→B = −4+3+(−3) = −4 < 0 + """ + cycle_edges = NEG_EDGES + [('C', 'B', -3)] + + fig = plt.figure(figsize=(14, 5.5)) + fig.suptitle('Bellman-Ford — wykrywanie cyklu ujemnego\n' + 'Dodano krawędź C→B(−3). Cykl: B→A→C→B = −4+3+(−3) = −4 < 0', + fontsize=FS_TITLE + 1, fontweight='bold', y=0.99) + + # Panel 1: Graph with cycle highlighted + ax1 = fig.add_subplot(1, 3, 1) + draw_neg_graph(ax1, NEG_EDGES, + title='Graf z cyklem ujemnym\nDodana krawędź C→B(−3) — przerywana', + dist={'S': '0', 'A': '?', 'B': '?', 'C': '?'}, + extra_edges=[('C', 'B', -3)]) + # Mark cycle + ax1.annotate("CYKL\n−4+3+(−3)=−4<0", + xy=(3.3, 2.0), + fontsize=FS, fontweight='bold', color='#D32F2F', + ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_RED, + edgecolor='#D32F2F', alpha=0.9)) + + # Panel 2: After V−1 iterations — still changing + ax2 = fig.add_subplot(1, 3, 2) + draw_neg_graph(ax2, NEG_EDGES, + title='Po V−1=3 iteracjach\ndist wciąż maleje (niestabilne!)', + dist={'S': '0', 'A': '−7', 'B': '−4', 'C': '−4'}, + visited={'S', 'A', 'B', 'C'}, + error_nodes={'A', 'B', 'C'}, + extra_edges=[('C', 'B', -3)]) + ax2.text(3.2, -0.4, + 'Każde okrążenie cyklu\nzmniejsza dist o 4.\n' + 'Dist → −∞ (brak minimum!)', + ha='center', va='top', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_RED, + edgecolor='#D32F2F')) + + # Panel 3: V-th iteration detects + ax3 = fig.add_subplot(1, 3, 3) + ax3.axis('off') + ax3.set_xlim(0, 10) + ax3.set_ylim(0, 10) + + detection_text = ( + "V-ta iteracja (sprawdzenie):\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" + "for (src, dst, w) in edges:\n" + " if dist[src]+w < dist[dst]:\n" + " return None # CYKL!\n\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" + "Sprawdzamy np. krawędź B→A:\n" + " dist[B] + (−4) = −4 + (−4) = −8\n" + " −8 < dist[A] = −7\n" + " → NADAL SIĘ POPRAWIA!\n" + " → CYKL UJEMNY WYKRYTY!\n\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" + "Wynik: return None\n" + "(najkrótsza ścieżka nie istnieje)" + ) + ax3.text(5, 5, detection_text, ha='center', va='center', + fontsize=FS + 0.5, family='monospace', + bbox=dict(boxstyle='round,pad=0.6', facecolor=LIGHT_RED, + edgecolor='#D32F2F', lw=2)) + ax3.set_title('Wykrywanie — V-ta iteracja\nJeśli cokolwiek się poprawia → cykl ujemny!', + fontsize=FS, fontweight='bold', pad=5) + + # Bottom note + fig.text(0.5, 0.01, + 'Bez cyklu ujemnego: po V−1 iteracjach dist jest stabilne. ' + 'Z cyklem ujemnym: dist maleje w nieskończoność → V-ta iteracja to wykrywa.', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW, edgecolor=LN)) + + plt.tight_layout(rect=[0, 0.06, 1, 0.94]) + plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_negative_cycle.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ bellman_ford_negative_cycle.png") + + +if __name__ == '__main__': + print("Generating Bellman-Ford negative weight diagrams...") + generate_bf_negative_weights() + generate_bf_negative_cycle() + print(f"\nAll diagrams saved to {OUTPUT_DIR}/") diff --git a/pytania/generate_pattern_diagrams.py b/pytania/generate_pattern_diagrams.py index d57fb7b..594aee2 100644 --- a/pytania/generate_pattern_diagrams.py +++ b/pytania/generate_pattern_diagrams.py @@ -286,6 +286,220 @@ def generate_three_pillars(): print(f" Saved: {out}") +# ============================================================ +# 4. Filled-in Observer Pattern Card +# ============================================================ +def generate_observer_card_filled(): + fig, ax = plt.subplots(figsize=(8.27, 8.5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.set_aspect('equal') + ax.axis('off') + fig.patch.set_facecolor(BG) + ax.set_title('Wypełniona karta wzorca — Observer (GoF)', + fontsize=FS_TITLE, fontweight='bold', pad=15) + + # Main card outline + card_x, card_y, card_w, card_h = 0.8, 0.3, 8.4, 9.2 + card = FancyBboxPatch((card_x, card_y), card_w, card_h, + boxstyle="round,pad=0.15", lw=2.5, + edgecolor=LN, facecolor=GRAY4) + ax.add_patch(card) + + # Fields with actual Observer content + fields = [ + ("Na", "NAZWA", "Observer", GRAY2, True), + ("P", "PROBLEM", "Obiekt (Subject) zmienia stan → wielu zależnych\n" + "obiektów musi zareagować, ale Subject nie\n" + "powinien znać ich konkretnych typów.", GRAY1, False), + ("Si", "SIŁY", "• loose coupling (nie znać obserwatorów z nazwy)\n" + " vs koszt powiadomień (N obserwatorów = N wywołań)\n" + "• otwartość na rozszerzenia vs złożoność debugowania", 'white', False), + ("Ro", "ROZWIĄZANIE", "Subject przechowuje listę Observer.\n" + "Metody: attach(o), detach(o), notify().\n" + "notify() iteruje po liście i woła update()\n" + "na każdym obserwatorze.", GRAY1, False), + ("Ko", "KONSEKWENCJE", "(+) Luźne wiązanie — Subject ↔ Observer\n" + "(+) Nowi obserwatorzy bez zmian w Subject\n" + "(−) Kaskada powiadomień może być kosztowna\n" + "(−) Memory leaks jeśli nie detach()", 'white', False), + ] + + band_x = card_x + 0.3 + band_w = card_w - 0.6 + start_y = card_y + card_h - 0.65 + + for i, (abbr, title, content, fill, is_title_field) in enumerate(fields): + if is_title_field: + band_h = 0.7 + elif i == 1: + band_h = 1.3 + elif i == 2: + band_h = 1.4 + elif i == 3: + band_h = 1.5 + else: + band_h = 1.5 + + by = start_y - sum( + (0.7 if j == 0 else 1.3 if j == 1 else 1.4 if j == 2 else 1.5 if j == 3 else 1.5) + 0.15 + for j in range(i) + ) + + # Abbreviation circle + circle = plt.Circle((band_x + 0.35, by + band_h / 2), 0.28, + lw=1.5, edgecolor=LN, facecolor=GRAY3) + ax.add_patch(circle) + ax.text(band_x + 0.35, by + band_h / 2, abbr, ha='center', va='center', + fontsize=10, fontweight='bold') + + # Field box + fx = band_x + 0.8 + fw = band_w - 0.8 + rect = FancyBboxPatch((fx, by), fw, band_h, + boxstyle="round,pad=0.06", lw=1, + edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + + if is_title_field: + ax.text(fx + fw / 2, by + band_h / 2, f"{title}: {content}", + ha='center', va='center', fontsize=12, fontweight='bold') + else: + ax.text(fx + 0.15, by + band_h - 0.2, title, ha='left', va='center', + fontsize=FS, fontweight='bold') + ax.text(fx + 0.15, by + band_h / 2 - 0.15, content, ha='left', va='center', + fontsize=FS_SMALL, family='monospace', linespacing=1.3) + + # Arrow + if i < len(fields) - 1: + draw_arrow(ax, band_x + 0.35, by - 0.02, + band_x + 0.35, by - 0.13, lw=1.0) + + # Extra info at bottom + extra_y = 0.55 + extras = [ + "Powiązane: Mediator (centralizuje), Pub/Sub (rozproszony), MVC (View = Observer)", + "Znane użycia: Java Swing listeners, C# event/delegate, React useState, DOM addEventListener" + ] + for j, txt in enumerate(extras): + ax.text(card_x + card_w / 2, extra_y + (1 - j) * 0.25, txt, + ha='center', va='center', fontsize=FS_SMALL, fontstyle='italic', + color='#444444') + + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, 'q14_observer_card_filled.png') + fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {out}") + + +# ============================================================ +# 5. Pattern Language Navigation Graph +# ============================================================ +def generate_pattern_language_navigation(): + fig, ax = plt.subplots(figsize=(8.27, 9)) + ax.set_xlim(0, 12) + ax.set_ylim(0, 12) + ax.set_aspect('equal') + ax.axis('off') + fig.patch.set_facecolor(BG) + ax.set_title('Język wzorców — nawigacja „problem → wzorzec → nowy problem"', + fontsize=FS_TITLE, fontweight='bold', pad=15) + + # Node positions: (x, y, label, is_pattern, fill) + # Left column: problems; Right column: patterns + nodes = [ + # Problems (left, rounded rect, white) + (1.5, 10.5, "Monolith\nnie skaluje się", False, 'white'), + (1.5, 8.2, "Jak routować\nżądania do\nserwisów?", False, 'white'), + (1.5, 5.9, "Co gdy serwis\nnie odpowiada?", False, 'white'), + (1.5, 3.6, "Jak zachować\nspójność\ntransakcji?", False, 'white'), + (1.5, 1.3, "Jak odnaleźć\nadres serwisu?", False, 'white'), + + # Patterns (right, filled rect, gray) + (7.0, 9.3, "Microservices", True, GRAY2), + (7.0, 7.0, "API Gateway", True, GRAY2), + (7.0, 4.7, "Circuit Breaker", True, GRAY2), + (7.0, 2.4, "Saga", True, GRAY2), + (10.0, 5.9, "Service\nDiscovery", True, GRAY1), + ] + + # Draw nodes + node_w_prob = 2.8 + node_h_prob = 1.3 + node_w_pat = 2.5 + node_h_pat = 1.0 + + for nx, ny, label, is_pattern, fill in nodes: + if is_pattern: + w, h = node_w_pat, node_h_pat + rect = FancyBboxPatch((nx - w/2, ny - h/2), w, h, + boxstyle="round,pad=0.1", lw=2, + edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(nx, ny, label, ha='center', va='center', + fontsize=10, fontweight='bold') + else: + w, h = node_w_prob, node_h_prob + rect = FancyBboxPatch((nx - w/2, ny - h/2), w, h, + boxstyle="round,pad=0.1", lw=1.2, + edgecolor=LN, facecolor=fill, linestyle='--') + ax.add_patch(rect) + ax.text(nx, ny, label, ha='center', va='center', + fontsize=FS_SMALL, fontstyle='italic') + + # Arrows: problem → pattern (solid), pattern → problem (dashed label) + arrows = [ + # (x1, y1, x2, y2, label, style) + (2.9, 10.5, 5.75, 9.5, "rozwiązuje →", '->', 1.5), + (7.0, 8.8, 2.9, 8.5, "← rodzi problem", '->', 1.0), + (2.9, 8.0, 5.75, 7.2, "rozwiązuje →", '->', 1.5), + (7.0, 6.5, 2.9, 6.2, "← rodzi problem", '->', 1.0), + (2.9, 5.7, 5.75, 5.0, "rozwiązuje →", '->', 1.5), + (7.0, 4.2, 2.9, 3.9, "← rodzi problem", '->', 1.0), + (2.9, 3.3, 5.75, 2.6, "rozwiązuje →", '->', 1.5), + # Microservices → Service Discovery + (8.25, 9.0, 9.5, 6.5, "wymaga →", '->', 1.0), + # Problem → Service Discovery + (2.9, 1.3, 8.75, 5.6, "rozwiązuje →", '->', 1.2), + ] + + for x1, y1, x2, y2, label, style, lw in arrows: + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=LN, lw=lw, + connectionstyle="arc3,rad=0.05")) + mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + ax.text(mx, my + 0.2, label, ha='center', va='center', + fontsize=6.5, fontstyle='italic', color='#555555', + bbox=dict(boxstyle='round,pad=0.1', facecolor='white', + edgecolor='none', alpha=0.8)) + + # Legend + legend_y = 0.3 + # Problem node + r1 = FancyBboxPatch((1.0, legend_y - 0.2), 1.5, 0.4, + boxstyle="round,pad=0.05", lw=1, edgecolor=LN, + facecolor='white', linestyle='--') + ax.add_patch(r1) + ax.text(1.75, legend_y, "Problem", ha='center', va='center', fontsize=7) + # Pattern node + r2 = FancyBboxPatch((3.5, legend_y - 0.2), 1.5, 0.4, + boxstyle="round,pad=0.05", lw=1.5, edgecolor=LN, + facecolor=GRAY2) + ax.add_patch(r2) + ax.text(4.25, legend_y, "Wzorzec", ha='center', va='center', + fontsize=7, fontweight='bold') + ax.text(6.5, legend_y, + "Nawigacja: Problem → Wzorzec → Nowy Problem → Wzorzec → ...", + ha='left', va='center', fontsize=7, fontstyle='italic') + + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, 'q14_pattern_language_navigation.png') + fig.savefig(out, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {out}") + + # ============================================================ # Main # ============================================================ @@ -294,4 +508,6 @@ if __name__ == '__main__': generate_pattern_template() generate_catalog_map() generate_three_pillars() + generate_observer_card_filled() + generate_pattern_language_navigation() print("Done!") diff --git a/pytania/generate_q23_diagrams.py b/pytania/generate_q23_diagrams.py new file mode 100644 index 0000000..fb20e86 --- /dev/null +++ b/pytania/generate_q23_diagrams.py @@ -0,0 +1,1637 @@ +#!/usr/bin/env python3 +""" +Generate all diagrams for PYTANIE 23: Segmentacja obrazu. + +A4-compatible, monochrome-friendly (grays + one accent), 300 DPI. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +import matplotlib.patches as patches +from matplotlib.patches import FancyArrowPatch, FancyBboxPatch +import numpy as np +import os + +DPI = 300 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +# Color palette — monochrome-friendly +BLACK = '#000000' +WHITE = '#FFFFFF' +GRAY1 = '#F5F5F5' +GRAY2 = '#E0E0E0' +GRAY3 = '#BDBDBD' +GRAY4 = '#9E9E9E' +GRAY5 = '#757575' +GRAY6 = '#424242' +ACCENT = '#4A90D9' # single blue accent for highlights +ACCENT_LIGHT = '#B3D4FC' +RED_ACCENT = '#D32F2F' +GREEN_ACCENT = '#388E3C' + +FS = 9 +FS_TITLE = 11 +FS_SMALL = 7 +FS_TINY = 6 + + +# ============================================================ +# 1. OTSU — Bimodal histogram + within-class variance +# ============================================================ +def generate_otsu_bimodal(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.5)) + + # --- Panel 1: Bimodal histogram --- + ax = axes[0] + np.random.seed(42) + dark = np.random.normal(60, 20, 3000).clip(0, 255) + bright = np.random.normal(190, 25, 2000).clip(0, 255) + all_pixels = np.concatenate([dark, bright]) + + counts, bins, bars = ax.hist(all_pixels, bins=64, color=GRAY3, edgecolor=GRAY5, linewidth=0.5) + ax.axvline(x=128, color=RED_ACCENT, linewidth=2, linestyle='--', label='Próg Otsu T=128') + ax.fill_betweenx([0, max(counts)*1.1], 0, 128, alpha=0.12, color=ACCENT) + ax.fill_betweenx([0, max(counts)*1.1], 128, 255, alpha=0.12, color=RED_ACCENT) + ax.text(45, max(counts)*0.85, 'Klasa 0\n(tło)', ha='center', fontsize=FS, fontweight='bold', color=ACCENT) + ax.text(195, max(counts)*0.85, 'Klasa 1\n(obiekt)', ha='center', fontsize=FS, fontweight='bold', color=RED_ACCENT) + ax.annotate('Garb 1', xy=(60, max(counts)*0.6), fontsize=FS_SMALL, ha='center', + arrowprops=dict(arrowstyle='->', color=GRAY5), xytext=(30, max(counts)*0.45)) + ax.annotate('Garb 2', xy=(190, max(counts)*0.5), fontsize=FS_SMALL, ha='center', + arrowprops=dict(arrowstyle='->', color=GRAY5), xytext=(220, max(counts)*0.35)) + ax.set_xlabel('Jasność piksela (0–255)', fontsize=FS) + ax.set_ylabel('Liczba pikseli', fontsize=FS) + ax.set_title('Histogram bimodalny', fontsize=FS_TITLE, fontweight='bold') + ax.legend(fontsize=FS_SMALL, loc='upper right') + ax.set_xlim(0, 255) + + # --- Panel 2: Within-class variance explanation --- + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Wariancja wewnątrzklasowa', fontsize=FS_TITLE, fontweight='bold') + + y = 9.2 + texts = [ + ('Wariancja = jak bardzo wartości\nróżnią się od średniej', FS, 'black', 'normal'), + ('', 0, 'black', 'normal'), + ('Klasa 0 (piksele ≤ T):', FS, ACCENT, 'bold'), + (' wartości: 30, 50, 45, 60, 55', FS_SMALL, 'black', 'normal'), + (' średnia μ₀ = 48', FS_SMALL, 'black', 'normal'), + (' σ₀² = ((30-48)²+(50-48)²+...)/5 = 108', FS_SMALL, 'black', 'normal'), + ('', 0, 'black', 'normal'), + ('Klasa 1 (piksele > T):', FS, RED_ACCENT, 'bold'), + (' wartości: 180, 200, 190, 210, 195', FS_SMALL, 'black', 'normal'), + (' średnia μ₁ = 195', FS_SMALL, 'black', 'normal'), + (' σ₁² = ((180-195)²+...)/5 = 100', FS_SMALL, 'black', 'normal'), + ('', 0, 'black', 'normal'), + ('σ²_wewnątrz = w₀·σ₀² + w₁·σ₁²', FS, BLACK, 'bold'), + ('= 0.6·108 + 0.4·100 = 104.8', FS_SMALL, 'black', 'normal'), + ('', 0, 'black', 'normal'), + ('Otsu próbuje KAŻDE T: 0,1,...,255', FS_SMALL, GREEN_ACCENT, 'bold'), + ('Wybiera T dające MINIMUM σ²_wewnątrz', FS_SMALL, GREEN_ACCENT, 'bold'), + ] + for txt, size, color, weight in texts: + if txt == '': + y -= 0.25 + continue + ax.text(0.3, y, txt, fontsize=size, color=color, fontweight=weight, + va='top', transform=ax.transAxes if False else None) + y -= 0.55 + + # --- Panel 3: Jednorodność explanation --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('"Jednorodne" = małe σ²', fontsize=FS_TITLE, fontweight='bold') + + # Draw two clusters + np.random.seed(7) + # Good separation + c0 = np.random.normal(2, 0.4, 15) + c1 = np.random.normal(7, 0.4, 15) + y_pos_0 = np.random.uniform(6, 8, 15) + y_pos_1 = np.random.uniform(6, 8, 15) + ax.scatter(c0, y_pos_0, c=ACCENT, s=30, zorder=5, label='Klasa 0') + ax.scatter(c1, y_pos_1, c=RED_ACCENT, s=30, zorder=5, label='Klasa 1') + ax.axvline(x=4.5, color=GREEN_ACCENT, linewidth=2, linestyle='--') + ax.text(4.5, 8.8, 'T optymalny', ha='center', fontsize=FS_SMALL, color=GREEN_ACCENT, fontweight='bold') + ax.text(2, 5.3, 'σ₀² mała\n(skupione)', ha='center', fontsize=FS_SMALL, color=ACCENT) + ax.text(7, 5.3, 'σ₁² mała\n(skupione)', ha='center', fontsize=FS_SMALL, color=RED_ACCENT) + ax.text(5, 4, '→ σ²_wewnątrz MINIMALNA\n→ klasy JEDNORODNE\n→ dobra segmentacja!', + ha='center', fontsize=FS, fontweight='bold', color=GREEN_ACCENT) + + # Bad separation + c0b = np.random.normal(3.5, 1.5, 15) + c1b = np.random.normal(6, 1.5, 15) + y_pos_0b = np.random.uniform(1, 3, 15) + y_pos_1b = np.random.uniform(1, 3, 15) + ax.scatter(c0b, y_pos_0b, c=ACCENT, s=30, marker='x', zorder=5) + ax.scatter(c1b, y_pos_1b, c=RED_ACCENT, s=30, marker='x', zorder=5) + ax.axvline(x=4.5, color=GRAY4, linewidth=1, linestyle=':', ymin=0, ymax=0.35) + ax.text(5, 0.3, 'σ²_wewnątrz DUŻA → klasy mieszają się → zły próg', + ha='center', fontsize=FS_SMALL, color=GRAY5) + + ax.legend(fontsize=FS_SMALL, loc='upper left') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_otsu_bimodal.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_otsu_bimodal.png") + + +# ============================================================ +# 2. WATERSHED — Topographic flooding (not ASCII!) +# ============================================================ +def generate_watershed(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.8)) + + # --- Panel 1: Image as topographic surface --- + ax = axes[0] + x = np.linspace(0, 10, 200) + # Create a surface with two valleys and a ridge + surface = 3 * np.exp(-((x - 3)**2) / 1.5) + 4 * np.exp(-((x - 7)**2) / 1.2) + \ + 0.5 * np.sin(x * 2) + 1 + # Invert: valleys at objects (dark), peaks at boundaries (bright) + surface_inv = 6 - surface + 1 + + ax.fill_between(x, 0, surface_inv, color=GRAY2, alpha=0.7) + ax.plot(x, surface_inv, color=BLACK, linewidth=1.5) + + # Mark valleys + ax.annotate('Dolina 1\n(obiekt A)', xy=(3, surface_inv[60]), fontsize=FS_SMALL, + ha='center', va='bottom', + arrowprops=dict(arrowstyle='->', color=ACCENT), xytext=(1.5, 5.5)) + ax.annotate('Dolina 2\n(obiekt B)', xy=(7, surface_inv[140]), fontsize=FS_SMALL, + ha='center', va='bottom', + arrowprops=dict(arrowstyle='->', color=RED_ACCENT), xytext=(8.5, 5.5)) + # Mark ridge + ax.annotate('Grań\n(granica)', xy=(5, surface_inv[100]), fontsize=FS_SMALL, + ha='center', va='bottom', + arrowprops=dict(arrowstyle='->', color=GREEN_ACCENT), xytext=(5, 6.5)) + + ax.set_xlabel('Pozycja piksela', fontsize=FS) + ax.set_ylabel('Jasność (= wysokość)', fontsize=FS) + ax.set_title('Krok 1: obraz → teren', fontsize=FS_TITLE, fontweight='bold') + ax.set_ylim(0, 7) + + # --- Panel 2: Flooding --- + ax = axes[1] + ax.fill_between(x, 0, surface_inv, color=GRAY2, alpha=0.7) + ax.plot(x, surface_inv, color=BLACK, linewidth=1.5) + + # Water level + water_level = 3.2 + water_mask_1 = (x < 5) & (surface_inv < water_level) + water_mask_2 = (x >= 5) & (surface_inv < water_level) + + # Fill water in valley 1 + x_v1 = x[(x > 1) & (x < 5)] + s_v1 = surface_inv[(x > 1) & (x < 5)] + ax.fill_between(x_v1, s_v1, water_level, where=s_v1 < water_level, + color=ACCENT_LIGHT, alpha=0.6) + # Fill water in valley 2 + x_v2 = x[(x > 5) & (x < 9)] + s_v2 = surface_inv[(x > 5) & (x < 9)] + ax.fill_between(x_v2, s_v2, water_level, where=s_v2 < water_level, + color='#FFCDD2', alpha=0.6) + + ax.axhline(y=water_level, color=ACCENT, linewidth=1, linestyle='--', alpha=0.5) + ax.text(3, 2.5, 'Woda A', fontsize=FS, ha='center', color=ACCENT, fontweight='bold') + ax.text(7, 2.2, 'Woda B', fontsize=FS, ha='center', color=RED_ACCENT, fontweight='bold') + ax.annotate('Tu się spotkają!\n→ GRANICA', xy=(5, surface_inv[100]), fontsize=FS_SMALL, + ha='center', color=GREEN_ACCENT, fontweight='bold', + arrowprops=dict(arrowstyle='->', color=GREEN_ACCENT), xytext=(5, 6.2)) + + ax.set_xlabel('Pozycja piksela', fontsize=FS) + ax.set_title('Krok 2: zalewanie', fontsize=FS_TITLE, fontweight='bold') + ax.set_ylim(0, 7) + + # --- Panel 3: Result with problem --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Krok 3: wynik', fontsize=FS_TITLE, fontweight='bold') + + # Good result + rect1 = FancyBboxPatch((0.5, 6), 3.5, 3.2, boxstyle="round,pad=0.1", + facecolor=ACCENT_LIGHT, edgecolor=BLACK, linewidth=1) + ax.add_patch(rect1) + ax.text(2.25, 8.8, 'Ideał: 2 segmenty', fontsize=FS, ha='center', fontweight='bold') + ax.text(2.25, 7.5, 'Segment A Segment B', fontsize=FS_SMALL, ha='center') + ax.text(2.25, 6.7, '(po marker-controlled)', fontsize=FS_SMALL, ha='center', color=GREEN_ACCENT) + + # Bad result (over-segmentation) + rect2 = FancyBboxPatch((5.5, 6), 4, 3.2, boxstyle="round,pad=0.1", + facecolor='#FFCDD2', edgecolor=BLACK, linewidth=1) + ax.add_patch(rect2) + ax.text(7.5, 8.8, 'Problem: over-segmentation', fontsize=FS, ha='center', fontweight='bold', + color=RED_ACCENT) + ax.text(7.5, 7.8, '47 regionów zamiast 2!', fontsize=FS_SMALL, ha='center', color=RED_ACCENT) + ax.text(7.5, 7.1, 'Każde mini-minimum', fontsize=FS_SMALL, ha='center') + ax.text(7.5, 6.5, '→ osobna „dolina"', fontsize=FS_SMALL, ha='center') + + # Solution: markers + rect3 = FancyBboxPatch((1, 0.5), 8, 4.5, boxstyle="round,pad=0.15", + facecolor=GRAY1, edgecolor=GREEN_ACCENT, linewidth=1.5) + ax.add_patch(rect3) + ax.text(5, 4.3, 'Rozwiązanie: Marker-controlled watershed', fontsize=FS, + ha='center', fontweight='bold', color=GREEN_ACCENT) + ax.text(5, 3.4, '1. Zaznacz ręcznie „seeds" (markery) w każdym obiekcie', fontsize=FS_SMALL, ha='center') + ax.text(5, 2.7, '2. Zalewaj TYLKO od tych markerów (nie od wszystkich minimów)', fontsize=FS_SMALL, ha='center') + ax.text(5, 2.0, '3. Eliminuje fałszywe doliny z szumu', fontsize=FS_SMALL, ha='center') + ax.text(5, 1.2, 'Wynik: tyle segmentów, ile podano markerów', fontsize=FS_SMALL, ha='center', + fontweight='bold') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_watershed.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_watershed.png") + + +# ============================================================ +# 3. MEAN SHIFT — Kernel, density, feature space +# ============================================================ +def generate_mean_shift(): + fig, axes = plt.subplots(1, 3, figsize=(11, 4)) + + # --- Panel 1: Feature space concept --- + ax = axes[0] + np.random.seed(42) + # Three clusters in 2D feature space (brightness, x-position) + c1x = np.random.normal(2, 0.5, 40) + c1y = np.random.normal(2, 0.5, 40) + c2x = np.random.normal(6, 0.6, 35) + c2y = np.random.normal(7, 0.5, 35) + c3x = np.random.normal(8, 0.4, 25) + c3y = np.random.normal(3, 0.6, 25) + + ax.scatter(c1x, c1y, c=GRAY4, s=15, alpha=0.7, zorder=3) + ax.scatter(c2x, c2y, c=GRAY4, s=15, alpha=0.7, zorder=3) + ax.scatter(c3x, c3y, c=GRAY4, s=15, alpha=0.7, zorder=3) + + # Label peaks + ax.scatter([2], [2], c=RED_ACCENT, s=80, marker='*', zorder=5, label='Max gęstości') + ax.scatter([6], [7], c=RED_ACCENT, s=80, marker='*', zorder=5) + ax.scatter([8], [3], c=RED_ACCENT, s=80, marker='*', zorder=5) + + ax.set_xlabel('Cecha 1: jasność', fontsize=FS) + ax.set_ylabel('Cecha 2: pozycja x', fontsize=FS) + ax.set_title('Przestrzeń cech', fontsize=FS_TITLE, fontweight='bold') + ax.text(2, 0.3, 'Klaster 1\n(ciemne, lewo)', ha='center', fontsize=FS_TINY, color=GRAY6) + ax.text(6, 5.3, 'Klaster 2\n(jasne, prawo)', ha='center', fontsize=FS_TINY, color=GRAY6) + ax.text(8, 1.3, 'Klaster 3\n(jasne, dół)', ha='center', fontsize=FS_TINY, color=GRAY6) + ax.legend(fontsize=FS_SMALL, loc='upper left') + + # --- Panel 2: Kernel/window moving --- + ax = axes[1] + ax.scatter(c1x, c1y, c=ACCENT_LIGHT, s=15, alpha=0.7, zorder=3) + ax.scatter(c2x, c2y, c=GRAY3, s=15, alpha=0.7, zorder=3) + ax.scatter(c3x, c3y, c=GRAY3, s=15, alpha=0.7, zorder=3) + + # Show kernel movement + path_x = [4.5, 3.8, 3.0, 2.3, 2.05] + path_y = [4.0, 3.3, 2.7, 2.2, 2.03] + + for i, (px, py) in enumerate(zip(path_x, path_y)): + alpha = 0.3 + 0.15 * i + circle = plt.Circle((px, py), 1.2, fill=False, edgecolor=ACCENT, + linewidth=1.5, linestyle='--' if i < len(path_x)-1 else '-', + alpha=alpha) + ax.add_patch(circle) + if i < len(path_x) - 1: + ax.annotate('', xy=(path_x[i+1], path_y[i+1]), + xytext=(px, py), + arrowprops=dict(arrowstyle='->', color=RED_ACCENT, lw=1.5)) + + ax.scatter([path_x[0]], [path_y[0]], c=ACCENT, s=50, marker='o', zorder=5) + ax.scatter([path_x[-1]], [path_y[-1]], c=RED_ACCENT, s=80, marker='*', zorder=5) + + ax.text(4.5, 5.2, 'Start: losowy\npiksel', fontsize=FS_SMALL, ha='center', color=ACCENT) + ax.text(2.05, 0.5, 'Koniec: max\ngęstości', fontsize=FS_SMALL, ha='center', color=RED_ACCENT, + fontweight='bold') + ax.text(7, 8, 'Okno (jądro)\nprzesuwa się\ndo skupiska', fontsize=FS_SMALL, ha='center', + color=GRAY6, + bbox=dict(boxstyle='round', facecolor=GRAY1, edgecolor=GRAY3)) + + ax.set_xlabel('Cecha 1', fontsize=FS) + ax.set_ylabel('Cecha 2', fontsize=FS) + ax.set_title('Jądro → max gęstości', fontsize=FS_TITLE, fontweight='bold') + ax.set_xlim(0, 10) + ax.set_ylim(0, 9) + + # --- Panel 3: Why no K parameter --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Dlaczego bez K?', fontsize=FS_TITLE, fontweight='bold') + + y = 9.0 + lines = [ + ('K-means wymaga:', FS, RED_ACCENT, 'bold'), + (' „Podaj K=3 klastry"', FS_SMALL, 'black', 'normal'), + (' Problem: skąd wiesz ile klastrów?', FS_SMALL, GRAY5, 'normal'), + ('', 0, '', ''), + ('Mean Shift NIE wymaga K:', FS, GREEN_ACCENT, 'bold'), + (' Każdy piksel startuje → toczy się', FS_SMALL, 'black', 'normal'), + (' → trafia do najbliższego szczytu', FS_SMALL, 'black', 'normal'), + (' → ile szczytów = tyle segmentów', FS_SMALL, 'black', 'normal'), + (' → automatycznie!', FS_SMALL, GREEN_ACCENT, 'bold'), + ('', 0, '', ''), + ('Parametr: bandwidth (szerokość okna)', FS, 'black', 'bold'), + (' Duże okno → mało segmentów', FS_SMALL, 'black', 'normal'), + (' Małe okno → dużo segmentów', FS_SMALL, 'black', 'normal'), + ('', 0, '', ''), + ('Okno = jądro (kernel):', FS, 'black', 'bold'), + (' Koło o promieniu h wokół punktu.', FS_SMALL, 'black', 'normal'), + (' Oblicz średnią pikseli W oknie.', FS_SMALL, 'black', 'normal'), + (' Przesuń okno na tę średnią.', FS_SMALL, 'black', 'normal'), + (' Powtórz aż się zatrzyma.', FS_SMALL, 'black', 'normal'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.2 + continue + ax.text(0.5, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.5 + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_mean_shift.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_mean_shift.png") + + +# ============================================================ +# 4. NORMALIZED CUTS — Graph cut visualization +# ============================================================ +def generate_normalized_cuts(): + fig, axes = plt.subplots(1, 3, figsize=(11, 4)) + + # --- Panel 1: Image as graph --- + ax = axes[0] + ax.set_xlim(-0.5, 4.5) + ax.set_ylim(-0.5, 4.5) + ax.set_aspect('equal') + ax.set_title('Obraz → graf', fontsize=FS_TITLE, fontweight='bold') + + # Draw 4x4 pixel grid with colors + pixel_vals = np.array([ + [30, 35, 180, 190], + [40, 30, 185, 200], + [170, 180, 40, 35], + [190, 175, 30, 45], + ]) + for i in range(4): + for j in range(4): + v = pixel_vals[i, j] + gray_val = v / 255.0 + color = str(gray_val) + rect = patches.Rectangle((j - 0.4, 3 - i - 0.4), 0.8, 0.8, + facecolor=(gray_val, gray_val, gray_val), + edgecolor=BLACK, linewidth=0.8) + ax.add_patch(rect) + text_color = 'white' if v < 100 else 'black' + ax.text(j, 3 - i, str(v), ha='center', va='center', fontsize=FS_SMALL, + color=text_color, fontweight='bold') + + # Draw edges between adjacent pixels + for i in range(4): + for j in range(4): + # Right neighbor + if j < 3: + similarity = max(0, 1 - abs(pixel_vals[i, j] - pixel_vals[i, j+1]) / 255) + lw = similarity * 2.5 + 0.3 + alpha = similarity * 0.8 + 0.2 + ax.plot([j + 0.4, j + 0.6], [3 - i, 3 - i], color=GRAY5, + linewidth=lw, alpha=alpha) + # Bottom neighbor + if i < 3: + similarity = max(0, 1 - abs(pixel_vals[i, j] - pixel_vals[i+1, j]) / 255) + lw = similarity * 2.5 + 0.3 + alpha = similarity * 0.8 + 0.2 + ax.plot([j, j], [3 - i - 0.4, 3 - i - 0.6], color=GRAY5, + linewidth=lw, alpha=alpha) + + ax.text(2, -0.8, 'Grube linie = duże podobieństwo\n(silna krawędź grafu)', + ha='center', fontsize=FS_TINY, color=GRAY5) + ax.axis('off') + + # --- Panel 2: Cut concept --- + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Cięcie grafu (graph cut)', fontsize=FS_TITLE, fontweight='bold') + + # Draw two groups of nodes + # Group A (dark pixels) + positions_A = [(2, 7), (3, 8), (2, 5), (3, 6)] + positions_B = [(7, 7), (8, 8), (7, 5), (8, 6)] + + # Intra-group edges (thick = similar) + for i, (x1, y1) in enumerate(positions_A): + for x2, y2 in positions_A[i+1:]: + ax.plot([x1, x2], [y1, y2], color=ACCENT, linewidth=2, alpha=0.5) + for i, (x1, y1) in enumerate(positions_B): + for x2, y2 in positions_B[i+1:]: + ax.plot([x1, x2], [y1, y2], color=RED_ACCENT, linewidth=2, alpha=0.5) + + # Inter-group edges (thin = dissimilar) — these get cut + cut_edges = [((3, 8), (7, 7)), ((3, 6), (7, 5)), ((2, 5), (7, 5))] + for (x1, y1), (x2, y2) in cut_edges: + ax.plot([x1, x2], [y1, y2], color=GRAY4, linewidth=0.8, linestyle='--') + + # Draw nodes + for x, y in positions_A: + ax.scatter(x, y, c=ACCENT, s=120, zorder=5, edgecolors=BLACK, linewidth=0.8) + for x, y in positions_B: + ax.scatter(x, y, c='#FFCDD2', s=120, zorder=5, edgecolors=BLACK, linewidth=0.8) + + # Cut line + ax.plot([5, 5], [3.5, 9.5], color=RED_ACCENT, linewidth=2.5, linestyle='-', + zorder=4) + ax.text(5, 9.8, 'CIĘCIE', ha='center', fontsize=FS, fontweight='bold', color=RED_ACCENT) + + ax.text(2.5, 3.8, 'Segment A\n(ciemne piksele)', ha='center', fontsize=FS_SMALL, color=ACCENT) + ax.text(7.5, 3.8, 'Segment B\n(jasne piksele)', ha='center', fontsize=FS_SMALL, color=RED_ACCENT) + + # Formula + ax.text(5, 1.8, 'Ncut(A,B) = cut(A,B)/assoc(A,V)\n + cut(A,B)/assoc(B,V)', + ha='center', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round', facecolor=GRAY1, edgecolor=GRAY3)) + ax.text(5, 0.5, 'Minimalizuj Ncut → tnij SŁABE krawędzie\nzachowuj SILNE (wewnątrz grupy)', + ha='center', fontsize=FS_TINY, color=GRAY5) + + # --- Panel 3: Algorithm summary --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Algorytm Normalized Cuts', fontsize=FS_TITLE, fontweight='bold') + + steps = [ + ('1. Zbuduj graf', 'Piksele = węzły\nKrawędzie = podobieństwo sąsiadów\n(kolor, jasność, odległość)'), + ('2. Macierz podobieństwa W', 'W[i,j] = exp(-|kolori - kolorj|² / σ²)\n→ im podobniejsze, tym wyższa waga'), + ('3. Macierz stopni D', 'D[i,i] = Σ W[i,j]\n(suma wszystkich wag z węzła i)'), + ('4. Rozwiąż problem własny', '(D-W)·y = λ·D·y\n→ drugi najm. wektor własny y'), + ('5. Podziel wg y', 'y[i] > 0 → segment A\ny[i] ≤ 0 → segment B'), + ] + + y = 9.5 + for title, desc in steps: + ax.text(0.5, y, title, fontsize=FS, fontweight='bold', va='top') + y -= 0.4 + ax.text(0.8, y, desc, fontsize=FS_TINY, va='top', color=GRAY6) + y -= 1.2 + + ax.text(5, 0.3, 'Złożoność: O(n³) — wymaga eigen decomposition!', + ha='center', fontsize=FS_SMALL, fontweight='bold', color=RED_ACCENT) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_normalized_cuts.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_normalized_cuts.png") + + +# ============================================================ +# 5. RELU — Function plot +# ============================================================ +def generate_relu(): + fig, axes = plt.subplots(1, 2, figsize=(8, 3.5)) + + # --- Panel 1: ReLU plot --- + ax = axes[0] + x = np.linspace(-5, 5, 200) + relu = np.maximum(0, x) + ax.plot(x, relu, color=ACCENT, linewidth=2.5, label='ReLU(x) = max(0, x)') + ax.axhline(y=0, color=GRAY3, linewidth=0.5) + ax.axvline(x=0, color=GRAY3, linewidth=0.5) + ax.fill_between(x[x < 0], 0, 0, color=RED_ACCENT, alpha=0.1) + ax.fill_between(x[x >= 0], 0, relu[x >= 0], color=ACCENT, alpha=0.1) + + # Annotations + ax.annotate('x < 0 → output = 0\n(neuron „wyłączony")', xy=(-3, 0), + fontsize=FS_SMALL, ha='center', va='bottom', color=RED_ACCENT, + arrowprops=dict(arrowstyle='->', color=RED_ACCENT), xytext=(-3, 2)) + ax.annotate('x ≥ 0 → output = x\n(neuron „włączony")', xy=(3, 3), + fontsize=FS_SMALL, ha='center', va='bottom', color=ACCENT, + arrowprops=dict(arrowstyle='->', color=ACCENT), xytext=(3, 4.5)) + ax.scatter([0], [0], c=BLACK, s=40, zorder=5) + ax.text(0.3, -0.5, '(0,0)', fontsize=FS_SMALL, color=GRAY5) + ax.set_xlabel('x (wejście neuronu)', fontsize=FS) + ax.set_ylabel('ReLU(x)', fontsize=FS) + ax.set_title('ReLU — Rectified Linear Unit', fontsize=FS_TITLE, fontweight='bold') + ax.legend(fontsize=FS_SMALL, loc='upper left') + ax.set_ylim(-1, 6) + ax.grid(True, alpha=0.2) + + # --- Panel 2: Why ReLU --- + ax = axes[1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Dlaczego ReLU?', fontsize=FS_TITLE, fontweight='bold') + + y = 9.0 + lines = [ + ('Neuron oblicza:', FS, BLACK, 'bold'), + (' z = w₁·x₁ + w₂·x₂ + ... + bias', FS_SMALL, BLACK, 'normal'), + (' output = ReLU(z) = max(0, z)', FS_SMALL, ACCENT, 'bold'), + ('', 0, '', ''), + ('Przykład:', FS, BLACK, 'bold'), + (' wagi: w₁=0.5, w₂=-0.3, bias=0.1', FS_SMALL, BLACK, 'normal'), + (' wejścia: x₁=2.0, x₂=4.0', FS_SMALL, BLACK, 'normal'), + (' z = 0.5·2 + (-0.3)·4 + 0.1 = -0.1', FS_SMALL, BLACK, 'normal'), + (' ReLU(-0.1) = max(0, -0.1) = 0', FS_SMALL, RED_ACCENT, 'bold'), + (' → neuron milczy (wejście nieistotne)', FS_SMALL, GRAY5, 'normal'), + ('', 0, '', ''), + ('Gdyby z = 2.3:', FS, BLACK, 'bold'), + (' ReLU(2.3) = max(0, 2.3) = 2.3', FS_SMALL, GREEN_ACCENT, 'bold'), + (' → neuron aktywny! Przekazuje sygnał', FS_SMALL, GRAY5, 'normal'), + ('', 0, '', ''), + ('Szybsza niż sigmoid/tanh', FS_SMALL, GRAY5, 'normal'), + ('(brak exp() → szybkie obliczenia)', FS_SMALL, GRAY5, 'normal'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.2 + continue + ax.text(0.5, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.5 + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_relu.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_relu.png") + + +# ============================================================ +# 6. DOT PRODUCT — Iloczyn skalarny visual +# ============================================================ +def generate_dot_product(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.5)) + + # --- Panel 1: Concept --- + ax = axes[0] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Iloczyn skalarny\n(dot product)', fontsize=FS_TITLE, fontweight='bold') + + y = 8.5 + lines = [ + ('Dwa wektory (listy liczb) → JEDNA liczba', FS, BLACK, 'bold'), + ('', 0, '', ''), + ('a = [a₁, a₂, a₃] b = [b₁, b₂, b₃]', FS, ACCENT, 'normal'), + ('', 0, '', ''), + ('a · b = a₁·b₁ + a₂·b₂ + a₃·b₃', FS, BLACK, 'bold'), + ('', 0, '', ''), + ('Przykład:', FS, BLACK, 'bold'), + ('a = [1, 3, -2] b = [4, -1, 5]', FS_SMALL, BLACK, 'normal'), + ('a·b = 1·4 + 3·(-1) + (-2)·5', FS_SMALL, BLACK, 'normal'), + (' = 4 + (-3) + (-10) = -9', FS_SMALL, RED_ACCENT, 'bold'), + ('', 0, '', ''), + ('Duży wynik → wektory „podobne" (w tym samym kierunku)', FS_SMALL, GREEN_ACCENT, 'normal'), + ('Mały/ujemny → wektory „różne"', FS_SMALL, RED_ACCENT, 'normal'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.25 + continue + ax.text(0.5, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.55 + + # --- Panel 2: Convolution as dot product --- + ax = axes[1] + ax.set_xlim(-0.5, 5.5) + ax.set_ylim(-0.5, 5.5) + ax.set_aspect('equal') + ax.set_title('Konwolucja = iloczyn skalarny\nfiltra × fragment obrazu', fontsize=FS_TITLE, fontweight='bold') + + # Filter 3x3 + filter_vals = [[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]] + for i in range(3): + for j in range(3): + rect = patches.Rectangle((j - 0.4, 4 - i - 0.4), 0.8, 0.8, + facecolor=ACCENT_LIGHT, edgecolor=BLACK, linewidth=0.8) + ax.add_patch(rect) + ax.text(j, 4 - i, str(filter_vals[i][j]), ha='center', va='center', + fontsize=FS, fontweight='bold') + + ax.text(1, 1.5, 'Filtr', ha='center', fontsize=FS, fontweight='bold', color=ACCENT) + + # Image patch + img_vals = [[50, 50, 200], [50, 50, 200], [50, 50, 200]] + for i in range(3): + for j in range(3): + rect = patches.Rectangle((j + 2.6, 4 - i - 0.4), 0.8, 0.8, + facecolor=GRAY2, edgecolor=BLACK, linewidth=0.8) + ax.add_patch(rect) + ax.text(j + 3, 4 - i, str(img_vals[i][j]), ha='center', va='center', + fontsize=FS, fontweight='bold') + + ax.text(4, 1.5, 'Fragment\nobrazu', ha='center', fontsize=FS, fontweight='bold', color=GRAY5) + + ax.text(2.5, 0.5, '(-1)·50 + 0·50 + 1·200 +\n(-1)·50 + 0·50 + 1·200 +\n(-1)·50 + 0·50 + 1·200\n= 450 (krawędź!)', + ha='center', fontsize=FS_TINY, fontweight='bold', + bbox=dict(boxstyle='round', facecolor=GRAY1, edgecolor=GREEN_ACCENT)) + + ax.axis('off') + + # --- Panel 3: Vector visualization --- + ax = axes[2] + # Draw two vectors + ax.quiver(0, 0, 3, 4, angles='xy', scale_units='xy', scale=1, + color=ACCENT, width=0.025, label='a = [3, 4]') + ax.quiver(0, 0, 4, 1, angles='xy', scale_units='xy', scale=1, + color=RED_ACCENT, width=0.025, label='b = [4, 1]') + + # Show angle + theta = np.linspace(np.arctan2(1, 4), np.arctan2(4, 3), 30) + r = 1.5 + ax.plot(r * np.cos(theta), r * np.sin(theta), color=GREEN_ACCENT, linewidth=1.5) + ax.text(1.8, 1.3, 'θ', fontsize=FS, color=GREEN_ACCENT, fontweight='bold') + + ax.text(3.2, 4.2, 'a', fontsize=FS, color=ACCENT, fontweight='bold') + ax.text(4.2, 1.2, 'b', fontsize=FS, color=RED_ACCENT, fontweight='bold') + + ax.text(2.5, -1.0, 'a · b = |a|·|b|·cos(θ)\n= 3·4 + 4·1 = 16', + ha='center', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round', facecolor=GRAY1, edgecolor=GRAY3)) + ax.text(2.5, -2.0, 'Mały kąt θ → duży dot product\n= wektory „zgadają się"', + ha='center', fontsize=FS_TINY, color=GRAY5) + + ax.set_xlim(-0.5, 5.5) + ax.set_ylim(-2.5, 5.5) + ax.set_aspect('equal') + ax.grid(True, alpha=0.2) + ax.legend(fontsize=FS_SMALL, loc='upper left') + ax.set_title('Geometrycznie: kąt', fontsize=FS_TITLE, fontweight='bold') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_dot_product.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_dot_product.png") + + +# ============================================================ +# 7. FCN — FC vs Conv 1x1, skip connections +# ============================================================ +def generate_fcn(): + fig, axes = plt.subplots(2, 1, figsize=(10, 7)) + + # --- Panel 1: FC vs Conv 1x1 --- + ax = axes[0] + ax.set_xlim(0, 20) + ax.set_ylim(0, 6) + ax.axis('off') + ax.set_title('FC (Fully Connected) vs Conv 1×1', fontsize=FS_TITLE, fontweight='bold') + + # Classic CNN with FC + layer_info_fc = [ + (1.5, 'Obraz\n224×224×3', 2.2, GRAY2), + (4.5, 'Conv+Pool\n112×112×64', 1.8, GRAY2), + (7.5, 'Conv+Pool\n7×7×512', 1.0, GRAY2), + (10, 'Flatten\n25088', 0.5, ACCENT_LIGHT), + (12, 'FC\n4096', 0.5, ACCENT_LIGHT), + (14, 'FC\n1000', 0.3, ACCENT_LIGHT), + (16, '"Kot"', 0.3, '#FFCDD2'), + ] + + y_fc = 4.5 + for i, (x, label, w, color) in enumerate(layer_info_fc): + rect = FancyBboxPatch((x - w/2, y_fc - 0.6), w, 1.2, + boxstyle="round,pad=0.05", facecolor=color, + edgecolor=BLACK, linewidth=0.8) + ax.add_patch(rect) + ax.text(x, y_fc, label, ha='center', va='center', fontsize=FS_TINY) + if i < len(layer_info_fc) - 1: + next_x = layer_info_fc[i + 1][0] + ax.annotate('', xy=(next_x - layer_info_fc[i+1][2]/2, y_fc), + xytext=(x + w/2, y_fc), + arrowprops=dict(arrowstyle='->', color=GRAY5, lw=1)) + + ax.text(0.3, y_fc, 'CNN:', fontsize=FS, fontweight='bold', color=RED_ACCENT, va='center') + ax.text(12, y_fc + 1, 'PROBLEM: FC wymaga\nSTAŁEGO rozmiaru\n(np. 224×224)', + ha='center', fontsize=FS_SMALL, color=RED_ACCENT, fontweight='bold', + bbox=dict(boxstyle='round', facecolor='#FFCDD2', edgecolor=RED_ACCENT, alpha=0.3)) + + # FCN with Conv 1x1 + layer_info_fcn = [ + (1.5, 'Obraz\nH×W×3', 2.2, GRAY2), + (4.5, 'Conv+Pool\nH/2 × W/2\n×64', 1.8, GRAY2), + (7.5, 'Conv+Pool\nH/32 × W/32\n×512', 1.0, GRAY2), + (10.5, 'Conv 1×1\nH/32 × W/32\n×C', 0.8, '#C8E6C9'), + (13.5, 'Upsample\nH×W×C', 1.8, '#C8E6C9'), + (16.5, 'Mapa\nsegmentacji', 1.5, '#C8E6C9'), + ] + + y_fcn = 1.5 + for i, (x, label, w, color) in enumerate(layer_info_fcn): + rect = FancyBboxPatch((x - w/2, y_fcn - 0.7), w, 1.4, + boxstyle="round,pad=0.05", facecolor=color, + edgecolor=BLACK, linewidth=0.8) + ax.add_patch(rect) + ax.text(x, y_fcn, label, ha='center', va='center', fontsize=FS_TINY) + if i < len(layer_info_fcn) - 1: + next_x = layer_info_fcn[i + 1][0] + ax.annotate('', xy=(next_x - layer_info_fcn[i+1][2]/2, y_fcn), + xytext=(x + w/2, y_fcn), + arrowprops=dict(arrowstyle='->', color=GRAY5, lw=1)) + + ax.text(0.3, y_fcn, 'FCN:', fontsize=FS, fontweight='bold', color=GREEN_ACCENT, va='center') + ax.text(10.5, y_fcn + 1.2, 'Conv 1×1:\nkażdy piksel\nosobno × wagi\n(jak FC ale\nzachowuje H×W)', + ha='center', fontsize=FS_TINY, color=GREEN_ACCENT, + bbox=dict(boxstyle='round', facecolor='#C8E6C9', edgecolor=GREEN_ACCENT, alpha=0.3)) + + # --- Panel 2: What FC and Conv do --- + ax = axes[1] + ax.set_xlim(0, 20) + ax.set_ylim(0, 6) + ax.axis('off') + ax.set_title('Co robi warstwa FC? Co robi konwolucja?', fontsize=FS_TITLE, fontweight='bold') + + # FC explanation + rect = FancyBboxPatch((0.3, 3.2), 9, 2.5, boxstyle="round,pad=0.15", + facecolor=ACCENT_LIGHT, edgecolor=ACCENT, linewidth=1) + ax.add_patch(rect) + ax.text(4.8, 5.2, 'Fully Connected (FC)', fontsize=FS, fontweight='bold', ha='center') + ax.text(4.8, 4.5, 'KAŻDY neuron połączony z KAŻDYM wejściem\n' + '25 088 wejść × 4 096 neuronów = ~103 MLN wag!\n' + 'Traci informację GDZIE (przestrzenną)\n' + 'Wymaga STAŁEGO rozmiaru wejścia', + fontsize=FS_TINY, ha='center', va='top') + + # Conv explanation + rect = FancyBboxPatch((10.3, 3.2), 9, 2.5, boxstyle="round,pad=0.15", + facecolor='#C8E6C9', edgecolor=GREEN_ACCENT, linewidth=1) + ax.add_patch(rect) + ax.text(14.8, 5.2, 'Konwolucja (Conv)', fontsize=FS, fontweight='bold', ha='center') + ax.text(14.8, 4.5, 'Filtr (np. 3×3) „jedzie" po obrazie\n' + 'Te same wagi dla KAŻDEJ pozycji\n' + 'Zachowuje informację GDZIE\n' + 'Akceptuje DOWOLNY rozmiar wejścia', + fontsize=FS_TINY, ha='center', va='top') + + # Conv 1x1 explanation + rect = FancyBboxPatch((3, 0.3), 14, 2.2, boxstyle="round,pad=0.15", + facecolor=GRAY1, edgecolor=BLACK, linewidth=1) + ax.add_patch(rect) + ax.text(10, 2.1, 'Conv 1×1 = „FC per piksel"', fontsize=FS, fontweight='bold', ha='center') + ax.text(10, 1.5, 'Filtr 1×1: patrzy na JEDEN piksel, ale WSZYSTKIE kanały (512→C klas)\n' + 'Działa jak FC ale zachowuje mapę H×W → każdy piksel osobno klasyfikowany\n' + 'FCN: zamień FC na Conv1×1 → koniec z wymogiem stałego rozmiaru!', + fontsize=FS_TINY, ha='center', va='top') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_fc_vs_conv1x1.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_fc_vs_conv1x1.png") + + +# ============================================================ +# 8. U-NET ARCHITECTURE — Proper U-shaped diagram +# ============================================================ +def generate_unet(): + fig, ax = plt.subplots(1, 1, figsize=(10, 6)) + ax.set_xlim(-1, 21) + ax.set_ylim(-1, 12) + ax.axis('off') + ax.set_title('U-Net: architektura w kształcie litery U', fontsize=FS_TITLE + 1, fontweight='bold') + + # Encoder layers (going DOWN-LEFT) + encoder_layers = [ + (2, 10, 2.5, 1.5, '572×572×1\n(wejście)', 64), + (2, 7.5, 2.2, 1.3, '284×284\n×64', 64), + (2, 5, 1.8, 1.1, '140×140\n×128', 128), + (2, 2.5, 1.5, 1.0, '68×68\n×256', 256), + ] + + # Bottleneck + bottleneck = (8, 0.5, 2.5, 1.2, '32×32×512\n(bottleneck)', 512) + + # Decoder layers (going UP-RIGHT) + decoder_layers = [ + (14, 2.5, 1.5, 1.0, '68×68\n×256', 256), + (14, 5, 1.8, 1.1, '140×140\n×128', 128), + (14, 7.5, 2.2, 1.3, '284×284\n×64', 64), + (14, 10, 2.5, 1.5, '572×572×C\n(mapa seg.)', 'C'), + ] + + def draw_block(ax, x, y, w, h, label, color): + rect = FancyBboxPatch((x - w/2, y - h/2), w, h, + boxstyle="round,pad=0.05", facecolor=color, + edgecolor=BLACK, linewidth=1.2) + ax.add_patch(rect) + ax.text(x, y, label, ha='center', va='center', fontsize=FS_TINY) + + # Draw encoder + for x, y, w, h, label, channels in encoder_layers: + draw_block(ax, x, y, w, h, label, ACCENT_LIGHT) + + # Draw arrows down (encoder) + for i in range(len(encoder_layers) - 1): + x1, y1 = encoder_layers[i][0], encoder_layers[i][1] - encoder_layers[i][3]/2 + x2, y2 = encoder_layers[i+1][0], encoder_layers[i+1][1] + encoder_layers[i+1][3]/2 + ax.annotate('', xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle='->', color=ACCENT, lw=2)) + ax.text(x1 - 1.7, (y1 + y2) / 2, 'MaxPool\n2×2\n↓ zmniejsz', fontsize=FS_TINY, + ha='center', color=ACCENT, fontweight='bold') + + # Encoder to bottleneck + x1, y1 = encoder_layers[-1][0], encoder_layers[-1][1] - encoder_layers[-1][3]/2 + draw_block(ax, bottleneck[0], bottleneck[1], bottleneck[2], bottleneck[3], + bottleneck[4], GRAY2) + ax.annotate('', xy=(bottleneck[0] - bottleneck[2]/2, bottleneck[1] + bottleneck[3]/2), + xytext=(x1, y1), + arrowprops=dict(arrowstyle='->', color=ACCENT, lw=2)) + + # Bottleneck to decoder + ax.annotate('', xy=(decoder_layers[0][0] - decoder_layers[0][2]/2, + decoder_layers[0][1] - decoder_layers[0][3]/2), + xytext=(bottleneck[0] + bottleneck[2]/2, bottleneck[1] + bottleneck[3]/2), + arrowprops=dict(arrowstyle='->', color=RED_ACCENT, lw=2)) + + # Draw decoder + for x, y, w, h, label, channels in decoder_layers: + color = '#C8E6C9' if channels != 'C' else '#A5D6A7' + draw_block(ax, x, y, w, h, label, color) + + # Draw arrows up (decoder) + for i in range(len(decoder_layers) - 1): + x1, y1 = decoder_layers[i][0], decoder_layers[i][1] + decoder_layers[i][3]/2 + x2, y2 = decoder_layers[i+1][0], decoder_layers[i+1][1] - decoder_layers[i+1][3]/2 + ax.annotate('', xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle='->', color=GREEN_ACCENT, lw=2)) + ax.text(x1 + 2, (y1 + y2) / 2, 'UpConv\n2×2\n↑ zwiększ', fontsize=FS_TINY, + ha='center', color=GREEN_ACCENT, fontweight='bold') + + # Skip connections (horizontal arrows) + skip_colors = [GRAY5, GRAY5, GRAY5, GRAY5] + for i in range(len(encoder_layers)): + enc = encoder_layers[i] + dec = decoder_layers[len(decoder_layers) - 1 - i] + ax.annotate('', xy=(dec[0] - dec[2]/2, dec[1]), + xytext=(enc[0] + enc[2]/2, enc[1]), + arrowprops=dict(arrowstyle='->', color=GRAY5, lw=1.5, + linestyle='dashed')) + mid_x = (enc[0] + enc[2]/2 + dec[0] - dec[2]/2) / 2 + ax.text(mid_x, enc[1] + 0.6, 'skip\n(concat)', fontsize=FS_TINY, + ha='center', color=GRAY5, fontweight='bold') + + # Labels + ax.text(0, 11.5, 'ENCODER\n(↓ zmniejsza)', fontsize=FS, fontweight='bold', color=ACCENT, + ha='center') + ax.text(17, 11.5, 'DECODER\n(↑ zwiększa)', fontsize=FS, fontweight='bold', color=GREEN_ACCENT, + ha='center') + ax.text(8, -0.8, 'Kształt litery „U": encoder schodzi ↓ → bottleneck na dnie → decoder wraca ↑', + fontsize=FS_SMALL, ha='center', color=GRAY5, fontweight='bold') + + # Concatenation explanation + rect = FancyBboxPatch((17.5, 3), 3, 5, boxstyle="round,pad=0.15", + facecolor=GRAY1, edgecolor=GRAY5, linewidth=1, linestyle='--') + ax.add_patch(rect) + ax.text(19, 7.5, 'Concatenation:', fontsize=FS_SMALL, ha='center', fontweight='bold') + ax.text(19, 6.5, 'Encoder: 64 kanały\nDecoder: 64 kanały\n→ concat → 128 kanałów\n\n' + 'Jak sklejenie\ndwóch stosów\nkart:', fontsize=FS_TINY, ha='center') + ax.text(19, 3.7, '[enc₁|enc₂|...|dec₁|dec₂|...]', fontsize=FS_TINY - 1, ha='center', + fontweight='bold', color=ACCENT) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_unet_arch.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_unet_arch.png") + + +# ============================================================ +# 9. RECEPTIVE FIELD — with dilation +# ============================================================ +def generate_receptive_field(): + fig, axes = plt.subplots(1, 3, figsize=(11, 4)) + + def draw_grid(ax, size, highlight_cells, highlight_color, title, grid_offset=(0, 0)): + ox, oy = grid_offset + for i in range(size): + for j in range(size): + color = WHITE + if (i, j) in highlight_cells: + color = highlight_color + rect = patches.Rectangle((ox + j, oy + size - 1 - i), 1, 1, + facecolor=color, edgecolor=GRAY4, linewidth=0.5) + ax.add_patch(rect) + ax.set_title(title, fontsize=FS_TITLE, fontweight='bold') + + # --- Panel 1: Standard 3x3 conv receptive field --- + ax = axes[0] + ax.set_xlim(-0.5, 7.5) + ax.set_ylim(-1, 8) + ax.set_aspect('equal') + ax.axis('off') + + # 7x7 input grid + highlight_3x3 = [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + draw_grid(ax, 7, highlight_3x3, ACCENT_LIGHT, 'Zwykła conv 3×3') + ax.text(3.5, -0.5, 'RF = 3×3 pikseli', fontsize=FS, ha='center', fontweight='bold', color=ACCENT) + + # --- Panel 2: Dilated conv (rate=2) --- + ax = axes[1] + ax.set_xlim(-0.5, 7.5) + ax.set_ylim(-1, 8) + ax.set_aspect('equal') + ax.axis('off') + + # 7x7 input grid with dilated highlights + highlight_dilated = [(1, 1), (1, 3), (1, 5), (3, 1), (3, 3), (3, 5), (5, 1), (5, 3), (5, 5)] + draw_grid(ax, 7, highlight_dilated, '#FFCDD2', 'Dilated conv 3×3\n(rate=2)') + ax.text(3.5, -0.5, 'RF = 5×5, ale 9 parametrów!', fontsize=FS, ha='center', + fontweight='bold', color=RED_ACCENT) + + # Connect dots to show pattern + dots_x = [1.5, 3.5, 5.5, 1.5, 3.5, 5.5, 1.5, 3.5, 5.5] + dots_y = [5.5, 5.5, 5.5, 3.5, 3.5, 3.5, 1.5, 1.5, 1.5] + ax.scatter(dots_x, dots_y, c=RED_ACCENT, s=30, zorder=5) + + # --- Panel 3: Comparison --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Receptive Field\n(pole widzenia neuronu)', fontsize=FS_TITLE, fontweight='bold') + + y = 8.5 + lines = [ + ('RF = ile pikseli WEJŚCIOWYCH', FS, BLACK, 'bold'), + ('wpływa na JEDEN piksel wyjścia', FS, BLACK, 'bold'), + ('', 0, '', ''), + ('Rate (współczynnik dylatacji):', FS, BLACK, 'bold'), + (' rate=1: filtr „dotyka" sąsiadów', FS_SMALL, BLACK, 'normal'), + (' rate=2: co drugi piksel → RF = 5×5', FS_SMALL, BLACK, 'normal'), + (' rate=3: co trzeci → RF = 7×7', FS_SMALL, BLACK, 'normal'), + (' WIĘCEJ kontekstu, TE SAME wagi!', FS_SMALL, GREEN_ACCENT, 'bold'), + ('', 0, '', ''), + ('Dlaczego ważne w segmentacji?', FS, BLACK, 'bold'), + (' Piksel sam nie wie czym jest.', FS_SMALL, BLACK, 'normal'), + (' Potrzebuje KONTEKSTU (otoczenia).', FS_SMALL, BLACK, 'normal'), + (' Większe RF → widzi obok budynki', FS_SMALL, BLACK, 'normal'), + (' → wie, że TEN piksel to „droga"', FS_SMALL, GREEN_ACCENT, 'bold'), + ('', 0, '', ''), + ('Global Average Pooling:', FS, BLACK, 'bold'), + (' Mapa H×W×C → 1×1×C', FS_SMALL, BLACK, 'normal'), + (' Średnia z CAŁEGO feature map', FS_SMALL, BLACK, 'normal'), + (' RF = nieskończone (cały obraz)', FS_SMALL, GREEN_ACCENT, 'bold'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.2 + continue + ax.text(0.5, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.45 + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_receptive_field.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_receptive_field.png") + + +# ============================================================ +# 10. TRANSFORMER / Self-attention / SOTA +# ============================================================ +def generate_transformer(): + fig, axes = plt.subplots(1, 3, figsize=(11, 4)) + + # --- Panel 1: CNN local vs Transformer global --- + ax = axes[0] + ax.set_xlim(-0.5, 8.5) + ax.set_ylim(-1.5, 8.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('CNN: widzi LOKALNIE', fontsize=FS_TITLE, fontweight='bold') + + # Draw 8x8 grid + for i in range(8): + for j in range(8): + color = WHITE + if 3 <= i <= 5 and 3 <= j <= 5: + color = ACCENT_LIGHT + rect = patches.Rectangle((j, 7 - i), 1, 1, + facecolor=color, edgecolor=GRAY3, linewidth=0.3) + ax.add_patch(rect) + + # Highlight center + rect = patches.Rectangle((4, 4), 1, 1, facecolor=RED_ACCENT, edgecolor=BLACK, linewidth=1.5, alpha=0.7) + ax.add_patch(rect) + ax.text(4.5, 4.5, '?', ha='center', va='center', fontsize=FS, fontweight='bold', color=WHITE) + ax.text(4.5, -0.8, 'Filtr 3×3 widzi tylko\n9 sąsiednich pikseli', fontsize=FS_SMALL, + ha='center', color=ACCENT) + + # --- Panel 2: Transformer global --- + ax = axes[1] + ax.set_xlim(-0.5, 8.5) + ax.set_ylim(-1.5, 8.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Transformer: widzi GLOBALNIE', fontsize=FS_TITLE, fontweight='bold') + + # Draw 8x8 grid all highlighted + for i in range(8): + for j in range(8): + color = '#FFCDD2' + rect = patches.Rectangle((j, 7 - i), 1, 1, + facecolor=color, edgecolor=GRAY3, linewidth=0.3) + ax.add_patch(rect) + + rect = patches.Rectangle((4, 4), 1, 1, facecolor=RED_ACCENT, edgecolor=BLACK, linewidth=1.5, alpha=0.9) + ax.add_patch(rect) + ax.text(4.5, 4.5, '?', ha='center', va='center', fontsize=FS, fontweight='bold', color=WHITE) + ax.text(4.5, -0.8, 'Self-attention „pyta"\nALL 64 piksele naraz', fontsize=FS_SMALL, + ha='center', color=RED_ACCENT) + + # --- Panel 3: SOTA + Transformer explanation --- + ax = axes[2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Transformer & SOTA', fontsize=FS_TITLE, fontweight='bold') + + y = 9.2 + lines = [ + ('Transformer:', FS, BLACK, 'bold'), + (' Architektura z 2017 (Vaswani et al.)', FS_SMALL, BLACK, 'normal'), + (' Oryginalnie do NLP (tłumaczenie)', FS_SMALL, BLACK, 'normal'), + (' Kluczowy mechanizm: SELF-ATTENTION', FS_SMALL, ACCENT, 'bold'), + ('', 0, '', ''), + ('Self-attention w skrócie:', FS, BLACK, 'bold'), + (' Każdy piksel tworzy trzy wektory:', FS_SMALL, BLACK, 'normal'), + (' Q (Query — „czego szukam?")', FS_SMALL, ACCENT, 'normal'), + (' K (Key — „co oferuję innych")', FS_SMALL, RED_ACCENT, 'normal'), + (' V (Value — „moja wartość")', FS_SMALL, GREEN_ACCENT, 'normal'), + (' Attention = softmax(Q·Kᵀ/√d)·V', FS_SMALL, BLACK, 'bold'), + (' Koszt: O(n²) — n=liczba pikseli', FS_SMALL, RED_ACCENT, 'normal'), + ('', 0, '', ''), + ('SOTA = State Of The Art:', FS, BLACK, 'bold'), + (' Najlepszy znany wynik na benchmarku', FS_SMALL, BLACK, 'normal'), + (' Np. „mIoU 85.1% na ADE20K = SOTA"', FS_SMALL, BLACK, 'normal'), + (' Ciągle się zmienia (nowy paper', FS_SMALL, GRAY5, 'normal'), + (' → nowy SOTA)', FS_SMALL, GRAY5, 'normal'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.15 + continue + ax.text(0.3, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.45 + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_transformer_attention.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_transformer_attention.png") + + +# ============================================================ +# 11. REGION GROWING — seed selection + BFS +# ============================================================ +def generate_region_growing(): + fig, axes = plt.subplots(1, 3, figsize=(11, 4.2)) + + # --- Panel 1: Manual vs automatic seed --- + ax = axes[0] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Seed: ręcznie vs automatycznie', fontsize=FS_TITLE, fontweight='bold') + + y = 9.2 + lines = [ + ('Ręczny seed:', FS, ACCENT, 'bold'), + (' Użytkownik klika na obraz', FS_SMALL, BLACK, 'normal'), + (' → „tu jest obiekt, od tego zacznij"', FS_SMALL, BLACK, 'normal'), + (' Użycie: segmentacja interaktywna', FS_SMALL, GRAY5, 'normal'), + (' (np. Photoshop — magic wand tool)', FS_SMALL, GRAY5, 'normal'), + ('', 0, '', ''), + ('Automatyczny seed:', FS, RED_ACCENT, 'bold'), + (' 1. Histogram → lokalne maxima', FS_SMALL, BLACK, 'normal'), + (' (najczęstsza jasność → seed)', FS_SMALL, GRAY5, 'normal'), + (' 2. Grid: siatka co N pikseli', FS_SMALL, BLACK, 'normal'), + (' (np. seed co 50 px → 100 seedów)', FS_SMALL, GRAY5, 'normal'), + (' 3. Losowe próbkowanie', FS_SMALL, BLACK, 'normal'), + (' 4. Ekstrema lokalne gradientu', FS_SMALL, BLACK, 'normal'), + ('', 0, '', ''), + ('Dlaczego OR?', FS, GREEN_ACCENT, 'bold'), + (' Ręczny → precyzyjny, ale wolny', FS_SMALL, BLACK, 'normal'), + (' Auto → szybki, ale over-segmentation', FS_SMALL, BLACK, 'normal'), + ] + for txt, size, color, weight in lines: + if txt == '': + y -= 0.15 + continue + ax.text(0.3, y, txt, fontsize=size, color=color, fontweight=weight, va='top') + y -= 0.45 + + # --- Panel 2: Region growing step by step --- + ax = axes[1] + ax.set_xlim(-0.5, 6.5) + ax.set_ylim(-1.5, 7.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Region Growing: krok po kroku', fontsize=FS_TITLE, fontweight='bold') + + # 6x6 grid with values + pixel_grid = np.array([ + [150, 153, 148, 200, 210, 205], + [147, 155, 152, 195, 208, 200], + [145, 148, 160, 190, 195, 210], + [200, 195, 190, 155, 148, 150], + [210, 205, 200, 150, 152, 145], + [215, 208, 195, 148, 147, 155], + ]) + + # Region grown from seed (2,1) with threshold 20 + region_mask = np.array([ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ]) + + for i in range(6): + for j in range(6): + v = pixel_grid[i, j] + if region_mask[i, j] == 1 and v < 170: + color = ACCENT_LIGHT + elif region_mask[i, j] == 1: + color = GRAY2 + else: + color = WHITE + if i == 1 and j == 1: + color = '#FFD54F' # Seed + rect = patches.Rectangle((j, 5 - i), 1, 1, + facecolor=color, edgecolor=GRAY4, linewidth=0.5) + ax.add_patch(rect) + ax.text(j + 0.5, 5 - i + 0.5, str(v), ha='center', va='center', + fontsize=FS_TINY, fontweight='bold') + + # Mark seed + ax.annotate('SEED\n(155)', xy=(1.5, 4.5), fontsize=FS_SMALL, + ha='center', color=RED_ACCENT, fontweight='bold', + arrowprops=dict(arrowstyle='->', color=RED_ACCENT), xytext=(-0.5, 7)) + + ax.text(3, -0.8, 'Próg = 20\nNiebieski = region (|val - seed| < 20)', + fontsize=FS_TINY, ha='center', color=ACCENT) + + # --- Panel 3: BFS expansion --- + ax = axes[2] + ax.set_xlim(-0.5, 6.5) + ax.set_ylim(-1.5, 7.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Rosnący region (BFS)', fontsize=FS_TITLE, fontweight='bold') + + # Show expansion waves + wave_colors = ['#FFD54F', '#FFF176', '#FFF9C4', ACCENT_LIGHT, '#B3D4FC'] + wave_labels = ['Seed', 'Fala 1', 'Fala 2', 'Fala 3', 'Fala 4'] + waves = [ + [(1, 1)], # seed + [(0, 1), (1, 0), (1, 2), (2, 1)], # wave 1 + [(0, 0), (0, 2), (2, 0), (2, 2)], # wave 2 + ] + + for i in range(6): + for j in range(6): + color = WHITE + for w_idx, wave in enumerate(waves): + if (i, j) in wave: + color = wave_colors[w_idx] + rect = patches.Rectangle((j, 5 - i), 1, 1, + facecolor=color, edgecolor=GRAY4, linewidth=0.5) + ax.add_patch(rect) + + # Draw BFS arrows from seed + seed_x, seed_y = 1.5, 4.5 + for dx, dy, label in [(0, 1, ''), (0, -1, ''), (1, 0, ''), (-1, 0, '')]: + ax.annotate('', xy=(seed_x + dx * 0.7, seed_y + dy * 0.7), + xytext=(seed_x, seed_y), + arrowprops=dict(arrowstyle='->', color=RED_ACCENT, lw=1.2)) + + ax.text(3, -0.5, 'BFS: sprawdzaj sąsiadów,\ndodawaj podobne do kolejki', + fontsize=FS_TINY, ha='center', color=GRAY5) + + # Legend + for w_idx, (color, label) in enumerate(zip(wave_colors[:3], wave_labels[:3])): + rect = patches.Rectangle((4, 6.5 - w_idx * 0.7), 0.5, 0.5, + facecolor=color, edgecolor=GRAY4, linewidth=0.5) + ax.add_patch(rect) + ax.text(4.8, 6.75 - w_idx * 0.7, label, fontsize=FS_TINY, va='center') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_region_growing.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_region_growing.png") + + +# ============================================================ +# 12. DIY THRESHOLDING — Step-by-step example +# ============================================================ +def generate_diy_thresholding(): + fig, axes = plt.subplots(2, 3, figsize=(11, 7)) + + np.random.seed(42) + + # Create a simple synthetic image: dark circle on bright background + size = 64 + img = np.ones((size, size)) * 200 # bright background + yy, xx = np.mgrid[:size, :size] + mask = ((xx - 32)**2 + (yy - 32)**2) < 15**2 + img[mask] = 60 # dark circle + # Add some noise + img += np.random.normal(0, 10, img.shape) + img = np.clip(img, 0, 255) + + # --- Panel 1: Original image --- + ax = axes[0, 0] + ax.imshow(img, cmap='gray', vmin=0, vmax=255) + ax.set_title('Krok 1: obraz wejściowy', fontsize=FS, fontweight='bold') + ax.axis('off') + ax.text(32, -3, '64×64 pikseli, szare', fontsize=FS_TINY, ha='center') + + # --- Panel 2: Histogram --- + ax = axes[0, 1] + counts, bins, _ = ax.hist(img.ravel(), bins=50, color=GRAY3, edgecolor=GRAY5, linewidth=0.5) + ax.axvline(x=128, color=RED_ACCENT, linewidth=2, linestyle='--', label='T=128 (Otsu)') + ax.set_xlabel('Jasność', fontsize=FS_SMALL) + ax.set_ylabel('Piksele', fontsize=FS_SMALL) + ax.set_title('Krok 2: histogram\n(bimodalny!)', fontsize=FS, fontweight='bold') + ax.legend(fontsize=FS_TINY) + ax.annotate('Garb 1\n(obiekt)', xy=(60, max(counts)*0.5), fontsize=FS_TINY, ha='center', + color=ACCENT, fontweight='bold') + ax.annotate('Garb 2\n(tło)', xy=(200, max(counts)*0.5), fontsize=FS_TINY, ha='center', + color=RED_ACCENT, fontweight='bold') + + # --- Panel 3: Thresholding result --- + ax = axes[0, 2] + binary = (img > 128).astype(float) + ax.imshow(binary, cmap='gray', vmin=0, vmax=1) + ax.set_title('Krok 3: progowanie T=128', fontsize=FS, fontweight='bold') + ax.axis('off') + ax.text(32, -3, 'Biały = tło, Czarny = obiekt', fontsize=FS_TINY, ha='center') + + # --- Panel 4: What Otsu does (variance plot) --- + ax = axes[1, 0] + # Compute within-class variance for each threshold + thresholds = range(10, 245) + variances = [] + for t in thresholds: + c0 = img[img <= t].ravel() + c1 = img[img > t].ravel() + if len(c0) == 0 or len(c1) == 0: + variances.append(np.nan) + continue + w0 = len(c0) / len(img.ravel()) + w1 = len(c1) / len(img.ravel()) + var = w0 * np.var(c0) + w1 * np.var(c1) + variances.append(var) + + ax.plot(list(thresholds), variances, color=ACCENT, linewidth=1.5) + best_t = list(thresholds)[np.nanargmin(variances)] + ax.axvline(x=best_t, color=RED_ACCENT, linewidth=1.5, linestyle='--', label=f'Otsu T={best_t}') + ax.scatter([best_t], [np.nanmin(variances)], c=RED_ACCENT, s=60, zorder=5) + ax.set_xlabel('Próg T', fontsize=FS_SMALL) + ax.set_ylabel('σ² wewnątrzklasowa', fontsize=FS_SMALL) + ax.set_title('Krok 4: Otsu szuka min σ²', fontsize=FS, fontweight='bold') + ax.legend(fontsize=FS_TINY) + + # --- Panel 5: Pseudocode --- + ax = axes[1, 1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Pseudokod Otsu', fontsize=FS, fontweight='bold') + + code_lines = [ + 'best_T = 0', + 'min_var = ∞', + '', + 'for T in 0..255:', + ' c0 = piksele z jasność ≤ T', + ' c1 = piksele z jasność > T', + ' w0 = len(c0) / len(all)', + ' w1 = len(c1) / len(all)', + ' var = w0·var(c0) + w1·var(c1)', + ' if var < min_var:', + ' min_var = var', + ' best_T = T', + '', + 'return best_T # optymalny próg', + ] + for i, line in enumerate(code_lines): + color = ACCENT if 'best_T = T' in line or 'return' in line else BLACK + ax.text(0.5, 9.5 - i * 0.65, line, fontsize=FS_TINY, fontfamily='monospace', + color=color, fontweight='bold' if color == ACCENT else 'normal') + + # --- Panel 6: Final result with Otsu --- + ax = axes[1, 2] + binary_otsu = (img > best_t).astype(float) + ax.imshow(binary_otsu, cmap='gray', vmin=0, vmax=1) + ax.set_title(f'Krok 5: wynik Otsu (T={best_t})', fontsize=FS, fontweight='bold') + ax.axis('off') + ax.text(32, -3, 'Automatyczny próg!', fontsize=FS_TINY, ha='center', + color=GREEN_ACCENT, fontweight='bold') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_diy_thresholding.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_diy_thresholding.png") + + +# ============================================================ +# 13. DIY U-NET — Simplified step-by-step +# ============================================================ +def generate_diy_unet(): + fig, axes = plt.subplots(2, 3, figsize=(11, 7)) + + np.random.seed(42) + size = 64 + + # Create synthetic image with two regions + img = np.ones((size, size, 3), dtype=np.uint8) * 200 # bright bg + # Dark region (object 1) + yy, xx = np.mgrid[:size, :size] + mask1 = ((xx - 20)**2 + (yy - 30)**2) < 12**2 + img[mask1] = [60, 60, 60] + # Medium region (object 2) + mask2 = ((xx - 45)**2 + (yy - 25)**2) < 8**2 + img[mask2] = [120, 120, 120] + + gt = np.zeros((size, size), dtype=np.uint8) + gt[mask1] = 1 # class 1 + gt[mask2] = 2 # class 2 + + # --- Panel 1: Input image --- + ax = axes[0, 0] + ax.imshow(img) + ax.set_title('Krok 1: obraz RGB\n64×64×3', fontsize=FS, fontweight='bold') + ax.axis('off') + + # --- Panel 2: Encoder shrinks --- + ax = axes[0, 1] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Krok 2: Encoder ZMNIEJSZA', fontsize=FS, fontweight='bold') + + sizes = [(64, 3), (32, 64), (16, 128), (8, 256)] + y_pos = 8.5 + for i, (s, c) in enumerate(sizes): + w = s / 64 * 4 + h = 0.8 + rect = FancyBboxPatch((5 - w/2, y_pos), w, h, + boxstyle="round,pad=0.05", facecolor=ACCENT_LIGHT, + edgecolor=ACCENT, linewidth=1) + ax.add_patch(rect) + ax.text(5, y_pos + h/2, f'{s}×{s}×{c}', ha='center', va='center', + fontsize=FS_SMALL, fontweight='bold') + if i < len(sizes) - 1: + ax.annotate('', xy=(5, y_pos - 0.3), xytext=(5, y_pos), + arrowprops=dict(arrowstyle='->', color=ACCENT, lw=1.5)) + ax.text(7, y_pos - 0.15, 'Conv+Pool', fontsize=FS_TINY, color=ACCENT) + y_pos -= 2.2 + + ax.text(5, 0.3, 'Wyciąga cechy:\nkrawędzie → tekstury → obiekty', + ha='center', fontsize=FS_TINY, color=GRAY5) + + # --- Panel 3: Bottleneck --- + ax = axes[0, 2] + # Show feature maps at bottleneck (abstract) + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Krok 3: Bottleneck\n(najbardziej abstrakcyjne cechy)', fontsize=FS, fontweight='bold') + + # Show small abstract feature maps + for k in range(4): + small = np.random.rand(4, 4) + ax_inset = fig.add_axes([0.68 + (k % 2) * 0.08, 0.72 - (k // 2) * 0.1, 0.06, 0.06]) + ax_inset.imshow(small, cmap='gray') + ax_inset.axis('off') + + ax.text(5, 5, '8×8×256\n\nMałe mapy, ale DUŻO kanałów\nKażdy kanał = jedna „cecha"\n' + '(np. kanał 42 = „wykrył koło"\n kanał 78 = „wykrył krawędź")\n\n' + 'Wie CO jest na obrazie\nale nie wie GDZIE dokładnie', + ha='center', va='center', fontsize=FS_SMALL, + bbox=dict(boxstyle='round', facecolor=GRAY1, edgecolor=GRAY3)) + + # --- Panel 4: Decoder enlarges --- + ax = axes[1, 0] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Krok 4: Decoder ZWIĘKSZA\n(+ skip connections!)', fontsize=FS, fontweight='bold') + + sizes_dec = [(8, 256), (16, 128), (32, 64), (64, 3)] + y_pos = 8.5 + for i, (s, c) in enumerate(sizes_dec): + w = s / 64 * 4 + h = 0.8 + rect = FancyBboxPatch((5 - w/2, y_pos), w, h, + boxstyle="round,pad=0.05", facecolor='#C8E6C9', + edgecolor=GREEN_ACCENT, linewidth=1) + ax.add_patch(rect) + label = f'{s}×{s}×{c}' + if i < len(sizes_dec) - 1: + label += ' + skip!' + ax.text(5, y_pos + h/2, label, ha='center', va='center', + fontsize=FS_SMALL, fontweight='bold') + if i < len(sizes_dec) - 1: + ax.annotate('', xy=(5, y_pos - 0.3), xytext=(5, y_pos), + arrowprops=dict(arrowstyle='->', color=GREEN_ACCENT, lw=1.5)) + ax.text(7, y_pos - 0.15, 'UpConv+Concat', fontsize=FS_TINY, color=GREEN_ACCENT) + y_pos -= 2.2 + + ax.text(5, 0.3, 'Odtwarza rozdzielczość:\nskip → przywraca krawędzie', + ha='center', fontsize=FS_TINY, color=GRAY5) + + # --- Panel 5: Output segmentation map --- + ax = axes[1, 1] + cmap = plt.cm.colors.ListedColormap([WHITE, ACCENT_LIGHT, '#FFCDD2']) + ax.imshow(gt, cmap=cmap, interpolation='nearest') + ax.set_title('Krok 5: mapa segmentacji\n64×64 (3 klasy)', fontsize=FS, fontweight='bold') + ax.axis('off') + ax.text(20, -3, 'Tło=0, obiekt A=1, obiekt B=2', fontsize=FS_TINY, ha='center') + + # --- Panel 6: Summary pseudocode --- + ax = axes[1, 2] + ax.set_xlim(0, 10) + ax.set_ylim(0, 10) + ax.axis('off') + ax.set_title('Pseudokod U-Net', fontsize=FS, fontweight='bold') + + code_lines = [ + '# ENCODER', + 'e1 = conv_block(input, 64) # 64×64', + 'e2 = conv_block(pool(e1), 128) # 32×32', + 'e3 = conv_block(pool(e2), 256) # 16×16', + '', + '# BOTTLENECK', + 'b = conv_block(pool(e3), 512) # 8×8', + '', + '# DECODER + SKIP', + 'd3 = conv_block(concat(', + ' upconv(b), e3), 256) # 16×16', + 'd2 = conv_block(concat(', + ' upconv(d3), e2), 128) # 32×32', + 'd1 = conv_block(concat(', + ' upconv(d2), e1), 64) # 64×64', + '', + 'output = conv_1x1(d1, n_classes)', + ] + for i, line in enumerate(code_lines): + color = ACCENT if 'concat' in line else (GREEN_ACCENT if 'output' in line else BLACK) + ax.text(0.3, 9.5 - i * 0.55, line, fontsize=FS_TINY, fontfamily='monospace', + color=color) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_diy_unet.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_diy_unet.png") + + +# ============================================================ +# 14. MNEMONICS — Visual mnemonic summary +# ============================================================ +def generate_mnemonics(): + fig, ax = plt.subplots(1, 1, figsize=(10, 8)) + ax.set_xlim(0, 20) + ax.set_ylim(0, 16) + ax.axis('off') + ax.set_title('Mnemoniki — segmentacja obrazu', fontsize=FS_TITLE + 2, fontweight='bold') + + def draw_card(ax, x, y, w, h, title, mnemonic, color, detail=''): + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15", + facecolor=color, edgecolor=BLACK, linewidth=1) + ax.add_patch(rect) + ax.text(x + w/2, y + h - 0.3, title, ha='center', va='top', + fontsize=FS, fontweight='bold') + ax.text(x + w/2, y + h/2 - 0.1, mnemonic, ha='center', va='center', + fontsize=FS_SMALL, fontstyle='italic', color=GRAY6) + if detail: + ax.text(x + w/2, y + 0.4, detail, ha='center', va='bottom', + fontsize=FS_TINY, color=GRAY5) + + # Title: STRATEGIE KLASYCZNE + ax.text(5, 15.5, 'STRATEGIE KLASYCZNE', fontsize=FS_TITLE, fontweight='bold', + color=ACCENT, ha='center') + + cards_classic = [ + (0.2, 12.5, 4.5, 2.5, 'Thresholding', '„PRÓG na bramce"\nPrzepuszcza > T,\nblokuje ≤ T', + ACCENT_LIGHT, 'jasne=1, ciemne=0'), + (5, 12.5, 4.5, 2.5, 'Otsu', '„AUTO-bramkarz"\nSam dobiera próg\nmin σ² wewnątrz', + ACCENT_LIGHT, 'histogram bimodalny'), + (0.2, 9.5, 4.5, 2.5, 'Region Growing', '„PLAMA rozlana"\nSeed → BFS po\npodobnych sąsiadach', + ACCENT_LIGHT, 'jak atrament na papierze'), + (5, 9.5, 4.5, 2.5, 'Watershed', '„ZALEWANIE terenu"\nDoliny=obiekty\nGranie=granice', + ACCENT_LIGHT, 'woda + geography'), + (0.2, 6.5, 4.5, 2.5, 'Mean Shift', '„KULKI toczą się"\nKażda → max gęstości\nBez K!', + ACCENT_LIGHT, 'bandwidth = okno'), + (5, 6.5, 4.5, 2.5, 'Normalized Cuts', '„CIĘCIE sznurków"\nGraf: tnij słabe\nkrawędzie (O(n³)!)', + ACCENT_LIGHT, 'eigenvector problem'), + ] + + for args in cards_classic: + draw_card(ax, *args) + + # Title: SIECI NEURONOWE + ax.text(15, 15.5, 'SIECI NEURONOWE', fontsize=FS_TITLE, fontweight='bold', + color=GREEN_ACCENT, ha='center') + + cards_nn = [ + (10.5, 12.5, 4.5, 2.5, 'FCN (2015)', '„FC → Conv 1×1"\nPierwsza end-to-end\nDowolny rozmiar', + '#C8E6C9', 'skip connections'), + (15.3, 12.5, 4.5, 2.5, 'U-Net (2015)', '„Litera U"\nEncoder↓ Decoder↑\nSkip = concat', + '#C8E6C9', 'medycyna, małe dane'), + (10.5, 9.5, 4.5, 2.5, 'DeepLab v3+', '„DZIURY w filtrze"\nAtrous conv (rate)\nASPP multi-scale', + '#C8E6C9', 'à trous = z dziurami'), + (15.3, 9.5, 4.5, 2.5, 'Transformer', '„WSZYSCY ze\nWSZYSTKIMI"\nSelf-attention O(n²)', + '#C8E6C9', 'SegFormer, Mask2Former'), + ] + + for args in cards_nn: + draw_card(ax, *args) + + # Metryki + ax.text(10, 8.3, 'METRYKI I LOSS', fontsize=FS_TITLE, fontweight='bold', + color=RED_ACCENT, ha='center') + + cards_metrics = [ + (10.5, 6.5, 4.5, 1.6, 'mIoU', '„Nakładka / Suma"\nIoU = A∩B / A∪B', + '#FFCDD2', ''), + (15.3, 6.5, 4.5, 1.6, 'Dice / Focal', '„Dice=2·nakładka"\nFocal=trudne px', + '#FFCDD2', ''), + ] + + for args in cards_metrics: + draw_card(ax, *args) + + # Master mnemonic at bottom + rect = FancyBboxPatch((1, 0.3), 18, 5.5, boxstyle="round,pad=0.2", + facecolor=GRAY1, edgecolor=BLACK, linewidth=1.5) + ax.add_patch(rect) + ax.text(10, 5.3, 'SUPER-MNEMONIK: kolejność algorytmów segmentacji', + ha='center', fontsize=FS, fontweight='bold') + ax.text(10, 4.5, '„TORW-MN FUD-T"', ha='center', fontsize=FS_TITLE + 2, + fontweight='bold', color=RED_ACCENT) + ax.text(10, 3.5, 'Klasyczne: Thresholding → Otsu → Region growing → Watershed → Mean shift → Norm. cuts', + ha='center', fontsize=FS_SMALL) + ax.text(10, 2.8, 'Neuronowe: FCN → U-Net → DeepLab → Transformer', + ha='center', fontsize=FS_SMALL) + ax.text(10, 1.8, '„Turyści Oglądają Rzekę, Wodospad, Morze, Nurt — Fotografują Uroczy Dwór Tajemnic"', + ha='center', fontsize=FS_SMALL, fontstyle='italic', color=ACCENT) + ax.text(10, 1.0, 'Klasyczne: proste→auto→BFS→flood→gęstość→graf | Neuronowe: FC→U-skip→dilated→attention', + ha='center', fontsize=FS_TINY, color=GRAY5) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'q23_mnemonics.png'), dpi=DPI, bbox_inches='tight', + facecolor='white') + plt.close() + print(" ✓ q23_mnemonics.png") + + +# ============================================================ +# MAIN +# ============================================================ +if __name__ == '__main__': + print("Generating PYTANIE 23 diagrams...") + generate_otsu_bimodal() + generate_watershed() + generate_mean_shift() + generate_normalized_cuts() + generate_relu() + generate_dot_product() + generate_fcn() + generate_unet() + generate_receptive_field() + generate_transformer() + generate_region_growing() + generate_diy_thresholding() + generate_diy_unet() + generate_mnemonics() + print(f"\nAll diagrams saved to: {OUTPUT_DIR}") diff --git a/pytania/generate_q24_diagrams.py b/pytania/generate_q24_diagrams.py new file mode 100644 index 0000000..11bb6b6 --- /dev/null +++ b/pytania/generate_q24_diagrams.py @@ -0,0 +1,1401 @@ +#!/usr/bin/env python3 +""" +Generate ALL diagrams for PYTANIE 24: Detekcja obiektów. +Monochrome, A4-printable PNGs (300 DPI). +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch, FancyArrowPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +FS_LABEL = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True, + edgecolor=LN, linestyle='-'): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=edgecolor, facecolor=fill, + linestyle=linestyle) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor, + facecolor=fill, linestyle=linestyle) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +def save_fig(fig, name): + path = os.path.join(OUTPUT_DIR, name) + fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG, pad_inches=0.15) + plt.close(fig) + print(f" Saved: {path}") + + +def draw_table(ax, headers, rows, x0, y0, col_widths, row_h=0.4, + header_fill=GRAY2, row_fills=None, fontsize=FS, header_fontsize=None): + if header_fontsize is None: + header_fontsize = fontsize + n_cols = len(headers) + cx = x0 + for j, hdr in enumerate(headers): + draw_box(ax, cx, y0, col_widths[j], row_h, hdr, fill=header_fill, + fontsize=header_fontsize, fontweight='bold', rounded=False) + cx += col_widths[j] + for i, row in enumerate(rows): + cy = y0 - (i + 1) * row_h + cx = x0 + fill = GRAY4 if (i % 2 == 0) else 'white' + if row_fills and i < len(row_fills): + fill = row_fills[i] + for j, cell in enumerate(row): + fw = 'bold' if j == 0 else 'normal' + draw_box(ax, cx, cy, col_widths[j], row_h, cell, fill=fill, + fontsize=fontsize, fontweight=fw, rounded=False) + cx += col_widths[j] + + +# ============================================================ +# 1. HOG + SVM Pipeline +# ============================================================ +def draw_hog_svm_pipeline(): + fig, ax = plt.subplots(figsize=(10, 4.5)) + ax.set_xlim(-0.5, 10.5) + ax.set_ylim(-1, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('HOG + SVM — pipeline detekcji pieszych', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + # Step 1: Image with sliding window + ax.add_patch(mpatches.Rectangle((0, 1.5), 2, 2, lw=1.5, edgecolor=LN, + facecolor=GRAY1)) + ax.text(1, 2.5, 'Obraz\nwejściowy', ha='center', va='center', fontsize=FS) + # sliding window overlay + ax.add_patch(mpatches.Rectangle((0.3, 1.8), 0.8, 1.2, lw=1.5, edgecolor='black', + facecolor='none', linestyle='--')) + ax.text(0.7, 1.35, 'okno 64×128', ha='center', va='center', fontsize=FS_SMALL, + style='italic') + + draw_arrow(ax, 2.1, 2.5, 2.8, 2.5, lw=1.5) + ax.text(2.45, 2.75, '①', ha='center', fontsize=FS_LABEL, fontweight='bold') + + # Step 2: Gradient computation + draw_box(ax, 2.9, 1.8, 1.6, 1.4, 'Oblicz\ngradienty\nGx, Gy', fill=GRAY4, + fontsize=FS) + ax.text(3.7, 1.55, 'kierunek + siła', ha='center', fontsize=FS_SMALL, style='italic') + + draw_arrow(ax, 4.6, 2.5, 5.2, 2.5, lw=1.5) + ax.text(4.9, 2.75, '②', ha='center', fontsize=FS_LABEL, fontweight='bold') + + # Step 3: HOG histogram + draw_box(ax, 5.3, 1.8, 1.6, 1.4, 'Histogramy\nkierunkowe\n9 binów/cel', + fill=GRAY4, fontsize=FS) + ax.text(6.1, 1.55, 'komórki 8×8 px', ha='center', fontsize=FS_SMALL, style='italic') + + draw_arrow(ax, 7.0, 2.5, 7.6, 2.5, lw=1.5) + ax.text(7.3, 2.75, '③', ha='center', fontsize=FS_LABEL, fontweight='bold') + + # Step 4: SVM + draw_box(ax, 7.7, 1.8, 1.4, 1.4, 'SVM\nklasyfikator\npieszy/tło', + fill=GRAY3, fontsize=FS, fontweight='bold') + + draw_arrow(ax, 9.2, 2.5, 9.7, 2.5, lw=1.5) + ax.text(9.45, 2.75, '④', ha='center', fontsize=FS_LABEL, fontweight='bold') + + # Step 5: NMS + output + draw_box(ax, 9.3, 2.0, 1.0, 1.0, 'NMS\n→ wynik', fill=GRAY1, fontsize=FS) + + # Bottom: HOG feature vector illustration + ax.text(5.0, 0.7, 'Wektor HOG: 3780 cech = 105 bloków × 4 komórki × 9 binów', + ha='center', fontsize=FS, style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + # Show small histogram bars + bar_x = 3.2 + bar_y = 0.0 + angles = [0, 20, 40, 60, 80, 100, 120, 140, 160] + values = [0.3, 0.1, 0.5, 0.8, 0.2, 0.6, 0.15, 0.4, 0.25] + for i, (a, v) in enumerate(zip(angles, values)): + ax.add_patch(mpatches.Rectangle((bar_x + i*0.18, bar_y), 0.15, v*0.6, + facecolor=GRAY3, edgecolor=LN, lw=0.5)) + ax.text(bar_x + 0.8, -0.2, '9 binów (0°–160°)', ha='center', fontsize=FS_SMALL) + + save_fig(fig, 'q24_hog_svm_pipeline.png') + + +# ============================================================ +# 2. HOG Gradient Step-by-Step +# ============================================================ +def draw_hog_gradient_steps(): + fig, axes = plt.subplots(1, 4, figsize=(12, 3.5)) + fig.suptitle('HOG — kroki obliczania cech', fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Step 1: Original patch + ax = axes[0] + patch = np.array([[50, 50, 200], + [50, 50, 200], + [50, 50, 200]]) + ax.imshow(patch, cmap='gray', vmin=0, vmax=255) + for i in range(3): + for j in range(3): + ax.text(j, i, str(patch[i, j]), ha='center', va='center', + fontsize=FS_LABEL, fontweight='bold', + color='white' if patch[i, j] > 127 else 'black') + ax.set_title('① Fragment obrazu\n(jasność pikseli)', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Step 2: Gradient magnitude + ax = axes[1] + gx = np.array([[0, 150, 0], + [0, 150, 0], + [0, 150, 0]]) + ax.imshow(gx, cmap='gray', vmin=0, vmax=255) + for i in range(3): + for j in range(3): + ax.text(j, i, str(gx[i, j]), ha='center', va='center', + fontsize=FS_LABEL, fontweight='bold', + color='white' if gx[i, j] > 100 else 'black') + ax.set_title('② Gradient Gx\n(krawędź pionowa!)', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Step 3: Cell histogram + ax = axes[2] + angles = ['0°', '20°', '40°', '60°', '80°', '100°', '120°', '140°', '160°'] + values = [150, 0, 0, 0, 0, 0, 0, 0, 0] + bars = ax.bar(range(9), values, color=GRAY3, edgecolor=LN, linewidth=0.5) + bars[0].set_facecolor(GRAY5) + ax.set_xticks(range(9)) + ax.set_xticklabels(angles, fontsize=5, rotation=45) + ax.set_title('③ Histogram komórki\n(bin 0° = krawędź pionowa)', fontsize=FS, fontweight='bold') + ax.set_ylabel('siła', fontsize=FS_SMALL) + + # Step 4: Block normalization + ax = axes[3] + # 2x2 block of cells + for i in range(2): + for j in range(2): + rect = mpatches.Rectangle((j*1.2, (1-i)*1.2), 1.0, 1.0, + lw=1.2, edgecolor=LN, facecolor=GRAY4) + ax.add_patch(rect) + ax.text(j*1.2+0.5, (1-i)*1.2+0.5, f'hist\n{i*2+j+1}', + ha='center', va='center', fontsize=FS_SMALL) + ax.add_patch(mpatches.Rectangle((-0.1, -0.1), 2.6, 2.6, + lw=2, edgecolor=LN, facecolor='none', + linestyle='--')) + ax.text(1.2, -0.4, 'blok 2×2 → L2-norm', ha='center', fontsize=FS_SMALL, + fontweight='bold') + ax.set_xlim(-0.3, 2.8) + ax.set_ylim(-0.7, 2.8) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('④ Normalizacja bloków\n(odporność na oświetlenie)', fontsize=FS, + fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_hog_gradient_steps.png') + + +# ============================================================ +# 3. Viola-Jones Cascade +# ============================================================ +def draw_viola_jones_cascade(): + fig, ax = plt.subplots(figsize=(10, 5)) + ax.set_xlim(-0.5, 10.5) + ax.set_ylim(-1.5, 5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Viola-Jones — kaskada klasyfikatorów (SITO)', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + # Input + draw_box(ax, -0.3, 2.5, 1.5, 1.2, '500 000\nokien', fill=GRAY1, fontsize=FS, + fontweight='bold') + + stages = [ + ('Etap 1\n2 cechy', '50%\nodrzucone', '250 000', GRAY4), + ('Etap 2\n10 cech', '80%\nodrzucone', '50 000', GRAY4), + ('Etap 3\n25 cech', '90%\nodrzucone', '5 000', GRAY4), + ('Etap 25\n200 cech', '99%\nodrzucone', '50', GRAY3), + ] + + x_pos = 1.6 + for i, (label, reject, remain, col) in enumerate(stages): + # Stage box + draw_box(ax, x_pos, 2.5, 1.6, 1.2, label, fill=col, fontsize=FS, + fontweight='bold') + + # Arrow from previous + draw_arrow(ax, x_pos - 0.3, 3.1, x_pos - 0.05, 3.1, lw=1.5) + + # Reject arrow down + draw_arrow(ax, x_pos + 0.8, 2.45, x_pos + 0.8, 1.6, lw=1.2) + ax.text(x_pos + 0.8, 1.3, reject, ha='center', fontsize=FS_SMALL, + color='black', style='italic') + ax.text(x_pos + 0.8, 0.8, '✗ NIE-TWARZ', ha='center', fontsize=FS_SMALL, + fontweight='bold') + + # Remaining count above + if i < len(stages) - 1: + ax.text(x_pos + 2.0, 3.9, f'→ {remain}', ha='center', fontsize=FS_SMALL, + style='italic') + + # Dots between stage 3 and stage 25 + if i == 2: + ax.text(x_pos + 2.0, 3.1, '· · ·', ha='center', fontsize=12, + fontweight='bold') + x_pos += 2.5 + else: + x_pos += 2.1 + + # Final output + draw_arrow(ax, x_pos + 0.3, 3.1, x_pos + 0.9, 3.1, lw=1.5) + draw_box(ax, x_pos + 0.5, 2.5, 1.3, 1.2, '~50\nTWARZE\n✓', fill=GRAY2, + fontsize=FS, fontweight='bold') + + # Timing info + ax.text(5.0, -0.5, 'Czas: 99% okien odrzucone w etapach 1-3 (~5 μs każde)\n' + 'Tylko 0.01% dochodzi do etapu 25 → cały obraz w ~30 ms = 30+ fps', + ha='center', fontsize=FS, style='italic', + bbox=dict(boxstyle='round,pad=0.4', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q24_viola_jones_cascade.png') + + +# ============================================================ +# 4. Haar Features +# ============================================================ +def draw_haar_features(): + fig, axes = plt.subplots(1, 4, figsize=(11, 3)) + fig.suptitle('Cechy Haar — typy i zastosowanie na twarzy', fontsize=FS_TITLE, + fontweight='bold', y=1.02) + + # Feature 1: Vertical edge + ax = axes[0] + ax.add_patch(mpatches.Rectangle((0, 0), 1, 2, facecolor=GRAY4, edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((1, 0), 1, 2, facecolor=GRAY3, edgecolor=LN, lw=1.5)) + ax.text(0.5, 1, '+Σ₁', ha='center', va='center', fontsize=FS_LABEL, fontweight='bold') + ax.text(1.5, 1, '−Σ₂', ha='center', va='center', fontsize=FS_LABEL, fontweight='bold') + ax.set_xlim(-0.2, 2.2) + ax.set_ylim(-0.5, 2.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Krawędź pionowa\nwartość = Σ₁ − Σ₂', fontsize=FS) + + # Feature 2: Horizontal edge + ax = axes[1] + ax.add_patch(mpatches.Rectangle((0, 1), 2, 1, facecolor=GRAY4, edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((0, 0), 2, 1, facecolor=GRAY3, edgecolor=LN, lw=1.5)) + ax.text(1, 1.5, '+Σ₁', ha='center', va='center', fontsize=FS_LABEL, fontweight='bold') + ax.text(1, 0.5, '−Σ₂', ha='center', va='center', fontsize=FS_LABEL, fontweight='bold') + ax.set_xlim(-0.2, 2.2) + ax.set_ylim(-0.5, 2.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Krawędź pozioma\n(oczy vs czoło)', fontsize=FS) + + # Feature 3: Three-rectangle (line) + ax = axes[2] + ax.add_patch(mpatches.Rectangle((0, 0), 0.7, 2, facecolor=GRAY3, edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((0.7, 0), 0.7, 2, facecolor=GRAY4, edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((1.4, 0), 0.7, 2, facecolor=GRAY3, edgecolor=LN, lw=1.5)) + ax.text(0.35, 1, '−Σ₁', ha='center', va='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(1.05, 1, '+Σ₂', ha='center', va='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(1.75, 1, '−Σ₃', ha='center', va='center', fontsize=FS_SMALL, fontweight='bold') + ax.set_xlim(-0.2, 2.3) + ax.set_ylim(-0.5, 2.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Linia (3 prostokąty)\n(nos vs policzki)', fontsize=FS) + + # Feature 4: Application on face (schematic) + ax = axes[3] + # Draw face outline (oval) + face = mpatches.Ellipse((1.2, 1.2), 2.0, 2.4, facecolor=GRAY4, + edgecolor=LN, lw=1.5) + ax.add_patch(face) + # Eyes (dark) + ax.add_patch(mpatches.Ellipse((0.7, 1.6), 0.4, 0.2, facecolor=GRAY3, + edgecolor=LN, lw=1)) + ax.add_patch(mpatches.Ellipse((1.7, 1.6), 0.4, 0.2, facecolor=GRAY3, + edgecolor=LN, lw=1)) + # Nose (light) + ax.plot([1.2, 1.1, 1.3], [1.3, 0.9, 0.9], color=LN, lw=1) + # Mouth + ax.plot([0.8, 1.0, 1.2, 1.4, 1.6], [0.55, 0.5, 0.55, 0.5, 0.55], + color=LN, lw=1) + # Haar feature overlay on eyes + ax.add_patch(mpatches.Rectangle((0.3, 1.4), 1.8, 0.4, facecolor='none', + edgecolor=LN, lw=2, linestyle='--')) + ax.annotate('cechy Haar\n(oczy ciemne\nvs czoło jasne)', xy=(1.2, 1.85), + xytext=(2.2, 2.3), fontsize=FS_SMALL, ha='center', + arrowprops=dict(arrowstyle='->', color=LN, lw=1)) + ax.set_xlim(-0.2, 3.0) + ax.set_ylim(-0.2, 2.8) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Zastosowanie na twarzy', fontsize=FS) + + fig.tight_layout() + save_fig(fig, 'q24_haar_features.png') + + +# ============================================================ +# 5. Integral Image +# ============================================================ +def draw_integral_image(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.5)) + fig.suptitle('Integral Image — suma prostokąta w O(1)', fontsize=FS_TITLE, + fontweight='bold', y=1.02) + + # Original image + ax = axes[0] + data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + ax.imshow(data, cmap='gray', vmin=0, vmax=10) + for i in range(3): + for j in range(3): + ax.text(j, i, str(data[i, j]), ha='center', va='center', + fontsize=12, fontweight='bold', color='white' if data[i,j] > 5 else 'black') + ax.set_title('① Obraz oryginalny', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Integral image + ax = axes[1] + ii = np.array([[1, 3, 6], [5, 12, 21], [12, 27, 45]]) + ax.imshow(ii, cmap='gray', vmin=0, vmax=50) + for i in range(3): + for j in range(3): + ax.text(j, i, str(ii[i, j]), ha='center', va='center', + fontsize=12, fontweight='bold', + color='white' if ii[i,j] > 25 else 'black') + ax.set_title('② Integral Image\n(sumy kumulatywne)', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Formula illustration + ax = axes[2] + ax.axis('off') + ax.set_xlim(0, 4) + ax.set_ylim(0, 4) + # Draw rectangle + ax.add_patch(mpatches.Rectangle((0.5, 0.5), 3, 3, facecolor='white', + edgecolor=LN, lw=1)) + ax.add_patch(mpatches.Rectangle((1.5, 0.5), 2, 2, facecolor=GRAY3, + edgecolor=LN, lw=2)) + # Labels + ax.text(0.3, 3.7, 'A', fontsize=12, fontweight='bold') + ax.text(3.6, 3.7, 'B', fontsize=12, fontweight='bold') + ax.text(0.3, 0.3, 'C', fontsize=12, fontweight='bold') + ax.text(3.6, 0.3, 'D', fontsize=12, fontweight='bold') + ax.text(2.5, 1.5, 'SZUKANA\nSUMA', ha='center', va='center', + fontsize=FS, fontweight='bold') + ax.text(2.0, -0.3, 'Suma = D − B − C + A\n= 4 odczyty → O(1) ZAWSZE!', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + ax.set_title('③ Formuła: 4 odczyty\n= O(1) niezależnie od rozmiaru', fontsize=FS, + fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_integral_image.png') + + +# ============================================================ +# 6. R-CNN Evolution +# ============================================================ +def draw_rcnn_evolution(): + fig, ax = plt.subplots(figsize=(11, 7)) + ax.set_xlim(-0.5, 11) + ax.set_ylim(-0.5, 7.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Ewolucja R-CNN: od 50s do 0.2s na obraz', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + y_positions = [5.5, 3.0, 0.5] + labels = ['R-CNN (2014) — 50 s/obraz', 'Fast R-CNN (2015) — 2 s/obraz', + 'Faster R-CNN (2015) — 0.2 s/obraz'] + + # R-CNN + y = y_positions[0] + ax.text(0, y + 1.3, labels[0], fontsize=FS_LABEL, fontweight='bold') + draw_box(ax, 0, y, 2, 0.9, 'Selective\nSearch', fill=GRAY2, fontsize=FS) + draw_arrow(ax, 2.1, y+0.45, 2.5, y+0.45) + ax.text(2.3, y+0.8, '~2000', ha='center', fontsize=FS_SMALL, style='italic') + draw_box(ax, 2.6, y, 1.5, 0.9, 'Resize\n224×224', fill=GRAY4, fontsize=FS) + draw_arrow(ax, 4.2, y+0.45, 4.6, y+0.45) + draw_box(ax, 4.7, y, 1.5, 0.9, 'CNN\n×2000!', fill=GRAY3, fontsize=FS, fontweight='bold') + draw_arrow(ax, 6.3, y+0.45, 6.7, y+0.45) + draw_box(ax, 6.8, y, 1.3, 0.9, 'SVM\nklasyf.', fill=GRAY4, fontsize=FS) + draw_arrow(ax, 8.2, y+0.45, 8.6, y+0.45) + draw_box(ax, 8.7, y, 1.0, 0.9, 'NMS', fill=GRAY1, fontsize=FS) + # Problem annotation + ax.text(5.5, y - 0.4, '⚠ CNN uruchamiane 2000× → 50 sek!', + ha='center', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Fast R-CNN + y = y_positions[1] + ax.text(0, y + 1.3, labels[1], fontsize=FS_LABEL, fontweight='bold') + draw_box(ax, 0, y, 2, 0.9, 'Selective\nSearch', fill=GRAY2, fontsize=FS) + draw_arrow(ax, 2.1, y+0.45, 2.5, y+0.45) + draw_box(ax, 2.6, y, 1.5, 0.9, 'CNN\n×1 (RAZ!)', fill=GRAY3, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 4.2, y+0.45, 4.6, y+0.45) + draw_box(ax, 4.7, y, 1.5, 0.9, 'ROI\nPooling', fill=GRAY1, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 6.3, y+0.45, 6.7, y+0.45) + draw_box(ax, 6.8, y, 1.3, 0.9, 'FC\nklasa+bbox', fill=GRAY4, fontsize=FS) + draw_arrow(ax, 8.2, y+0.45, 8.6, y+0.45) + draw_box(ax, 8.7, y, 1.0, 0.9, 'NMS', fill=GRAY1, fontsize=FS) + ax.text(3.8, y - 0.4, '✓ CNN RAZ na cały obraz → 25× szybciej', + ha='center', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Faster R-CNN + y = y_positions[2] + ax.text(0, y + 1.3, labels[2], fontsize=FS_LABEL, fontweight='bold') + draw_box(ax, 0.5, y, 1.5, 0.9, 'CNN\nBackbone', fill=GRAY3, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 2.1, y+0.45, 2.5, y+0.45) + draw_box(ax, 2.6, y, 1.5, 0.9, 'Feature\nMap', fill=GRAY1, fontsize=FS) + draw_arrow(ax, 4.2, y+0.45, 4.6, y+0.45) + draw_box(ax, 4.7, y, 1.3, 0.9, 'RPN\n(w sieci!)', fill=GRAY2, fontsize=FS, + fontweight='bold') + draw_arrow(ax, 6.1, y+0.45, 6.5, y+0.45) + draw_box(ax, 6.6, y, 1.3, 0.9, 'ROI\nPooling', fill=GRAY1, fontsize=FS) + draw_arrow(ax, 8.0, y+0.45, 8.4, y+0.45) + draw_box(ax, 8.5, y, 1.3, 0.9, 'FC\nklasa+bbox', fill=GRAY4, fontsize=FS) + ax.text(5.0, y - 0.4, '✓ RPN zastępuje Selective Search → end-to-end', + ha='center', fontsize=FS_SMALL, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q24_rcnn_evolution.png') + + +# ============================================================ +# 7. YOLO Grid +# ============================================================ +def draw_yolo_grid(): + fig, axes = plt.subplots(1, 3, figsize=(12, 4)) + fig.suptitle('YOLO — detekcja jednoetapowa (siatka S×S)', fontsize=FS_TITLE, + fontweight='bold', y=1.02) + + # Grid on image + ax = axes[0] + S = 7 + ax.set_xlim(0, S) + ax.set_ylim(0, S) + for i in range(S + 1): + ax.axhline(y=i, color=LN, lw=0.5, alpha=0.5) + ax.axvline(x=i, color=LN, lw=0.5, alpha=0.5) + ax.add_patch(mpatches.Rectangle((0, 0), S, S, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + # Highlight one cell + ax.add_patch(mpatches.Rectangle((3, 3), 1, 1, facecolor=GRAY2, + edgecolor=LN, lw=2)) + # Object center dot + ax.plot(3.5, 3.5, 'ko', markersize=8) + # Bounding box from that cell + ax.add_patch(mpatches.Rectangle((2.0, 2.2), 3.0, 2.6, facecolor='none', + edgecolor=LN, lw=2, linestyle='--')) + ax.text(3.5, 1.8, 'bbox z komórki (3,3)', ha='center', fontsize=FS_SMALL, + fontweight='bold') + ax.set_aspect('equal') + ax.invert_yaxis() + ax.set_title('① Siatka 7×7\nna obrazie', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Cell prediction + ax = axes[1] + ax.axis('off') + ax.set_xlim(0, 6) + ax.set_ylim(-1, 5) + + # Draw prediction vector + labels = ['x', 'y', 'w', 'h', 'conf', 'x', 'y', 'w', 'h', 'conf', + 'P(c₁)', '...', 'P(c₂₀)'] + colors_vec = [GRAY4]*5 + [GRAY2]*5 + [GRAY1]*3 + + bw = 0.42 + for i, (l, c) in enumerate(zip(labels, colors_vec)): + x_pos = 0.3 + i * bw + ax.add_patch(mpatches.Rectangle((x_pos, 2.5), bw-0.02, 0.6, + facecolor=c, edgecolor=LN, lw=0.8)) + ax.text(x_pos + bw/2, 2.8, l, ha='center', va='center', + fontsize=5, fontweight='bold') + + # Brackets for grouping + ax.annotate('', xy=(0.3, 2.4), xytext=(2.4, 2.4), + arrowprops=dict(arrowstyle='-', lw=1)) + ax.text(1.35, 2.15, 'bbox 1 (5 wartości)', ha='center', fontsize=FS_SMALL) + + ax.annotate('', xy=(2.4, 2.4), xytext=(4.5, 2.4), + arrowprops=dict(arrowstyle='-', lw=1)) + ax.text(3.45, 2.15, 'bbox 2 (5 wartości)', ha='center', fontsize=FS_SMALL) + + ax.annotate('', xy=(4.5, 2.4), xytext=(5.8, 2.4), + arrowprops=dict(arrowstyle='-', lw=1)) + ax.text(5.15, 2.15, '20 klas', ha='center', fontsize=FS_SMALL) + + ax.text(3.0, 3.5, 'Każda komórka → 30 wartości\n= 2×(x,y,w,h,conf) + 20 klas', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.set_title('② Predykcja jednej komórki\n(S=7, B=2, C=20)', fontsize=FS, + fontweight='bold') + + # Speed comparison + ax = axes[2] + ax.axis('off') + ax.set_xlim(0, 5) + ax.set_ylim(0, 5) + + methods = ['R-CNN', 'Fast R-CNN', 'Faster R-CNN', 'YOLO', 'YOLOv8'] + fps_vals = [0.02, 0.5, 5, 45, 100] + bar_colors = [GRAY3, GRAY3, GRAY3, GRAY2, GRAY1] + + for i, (m, f, c) in enumerate(zip(methods, fps_vals, bar_colors)): + bar_w = f / 100 * 4.0 + y_pos = 4.0 - i * 0.8 + ax.add_patch(mpatches.Rectangle((0.5, y_pos), max(bar_w, 0.1), 0.5, + facecolor=c, edgecolor=LN, lw=0.8)) + ax.text(0.4, y_pos + 0.25, m, ha='right', va='center', fontsize=FS, + fontweight='bold') + ax.text(max(0.7, 0.5 + bar_w + 0.1), y_pos + 0.25, f'{f} fps', + ha='left', va='center', fontsize=FS) + + ax.set_title('③ Porównanie szybkości\n(fps = klatki/sek)', fontsize=FS, + fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_yolo_grid.png') + + +# ============================================================ +# 8. IoU Diagram +# ============================================================ +def draw_iou_diagram(): + fig, axes = plt.subplots(1, 3, figsize=(11, 3.5)) + fig.suptitle('IoU (Intersection over Union) — miara nakładania bboxów', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Low IoU + ax = axes[0] + ax.add_patch(mpatches.Rectangle((0, 0), 3, 3, facecolor=GRAY4, + edgecolor=LN, lw=1.5, label='A')) + ax.add_patch(mpatches.Rectangle((2.5, 2.5), 3, 3, facecolor=GRAY2, + edgecolor=LN, lw=1.5, alpha=0.7, label='B')) + # Intersection + ax.add_patch(mpatches.Rectangle((2.5, 2.5), 0.5, 0.5, facecolor=GRAY3, + edgecolor=LN, lw=2)) + ax.text(1.5, 1.5, 'A', ha='center', va='center', fontsize=12, fontweight='bold') + ax.text(4, 4, 'B', ha='center', va='center', fontsize=12, fontweight='bold') + ax.set_xlim(-0.5, 6) + ax.set_ylim(-0.5, 6) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('IoU ≈ 0.04\n(prawie się nie nakładają)', fontsize=FS, fontweight='bold') + + # Medium IoU + ax = axes[1] + ax.add_patch(mpatches.Rectangle((0, 0), 3, 3, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((1.5, 1.5), 3, 3, facecolor=GRAY2, + edgecolor=LN, lw=1.5, alpha=0.7)) + ax.add_patch(mpatches.Rectangle((1.5, 1.5), 1.5, 1.5, facecolor=GRAY3, + edgecolor=LN, lw=2)) + ax.text(0.7, 0.7, 'A', ha='center', va='center', fontsize=12, fontweight='bold') + ax.text(3.5, 3.5, 'B', ha='center', va='center', fontsize=12, fontweight='bold') + ax.text(2.25, 2.25, '∩', ha='center', va='center', fontsize=14, fontweight='bold') + ax.set_xlim(-0.5, 5) + ax.set_ylim(-0.5, 5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('IoU ≈ 0.14\n(częściowe nakładanie)', fontsize=FS, fontweight='bold') + + # High IoU + ax = axes[2] + ax.add_patch(mpatches.Rectangle((0, 0), 3, 3, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((0.3, 0.3), 3, 3, facecolor=GRAY2, + edgecolor=LN, lw=1.5, alpha=0.7)) + ax.add_patch(mpatches.Rectangle((0.3, 0.3), 2.7, 2.7, facecolor=GRAY3, + edgecolor=LN, lw=2)) + ax.text(-0.3, -0.3, 'A', ha='center', va='center', fontsize=12, fontweight='bold') + ax.text(3.5, 3.5, 'B', ha='center', va='center', fontsize=12, fontweight='bold') + ax.text(1.65, 1.65, '∩', ha='center', va='center', fontsize=14, fontweight='bold') + ax.set_xlim(-0.8, 4) + ax.set_ylim(-0.8, 4) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('IoU ≈ 0.74\n(duże nakładanie → duplikat!)', fontsize=FS, fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_iou_diagram.png') + + +# ============================================================ +# 9. NMS Step-by-Step +# ============================================================ +def draw_nms_steps(): + fig, axes = plt.subplots(1, 3, figsize=(12, 4)) + fig.suptitle('NMS (Non-Maximum Suppression) — usuwanie duplikatów', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Before NMS + ax = axes[0] + ax.add_patch(mpatches.Rectangle((0, 0), 6, 5, facecolor=GRAY4, + edgecolor=LN, lw=1)) + # Multiple overlapping boxes for same object + ax.add_patch(mpatches.Rectangle((1, 1), 2.5, 3, facecolor='none', + edgecolor=LN, lw=2)) + ax.text(2.25, 4.2, 'conf=0.95', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.add_patch(mpatches.Rectangle((1.2, 1.3), 2.3, 2.8, facecolor='none', + edgecolor=LN, lw=1.5, linestyle='--')) + ax.text(2.35, 1.1, 'conf=0.90', ha='center', fontsize=FS_SMALL) + ax.add_patch(mpatches.Rectangle((0.8, 0.8), 2.7, 3.2, facecolor='none', + edgecolor=LN, lw=1, linestyle=':')) + ax.text(2.15, 0.6, 'conf=0.85', ha='center', fontsize=FS_SMALL) + # Different object + ax.add_patch(mpatches.Rectangle((4, 2), 1.5, 1.5, facecolor='none', + edgecolor=LN, lw=1.5)) + ax.text(4.75, 3.7, 'conf=0.80', ha='center', fontsize=FS_SMALL) + ax.text(2, 0.2, '⚠ 4 detekcje (3 duplikaty!)', ha='center', fontsize=FS_SMALL, + fontweight='bold') + ax.set_xlim(-0.3, 6.3) + ax.set_ylim(-0.3, 5.3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('① Przed NMS\n(wiele nakładających się)', fontsize=FS, fontweight='bold') + + # NMS process + ax = axes[1] + ax.axis('off') + ax.set_xlim(0, 6) + ax.set_ylim(0, 5) + + steps = [ + ('1. Sortuj: [0.95, 0.90, 0.85, 0.80]', 4.5), + ('2. Weź najlepszą (0.95) → ZACHOWAJ', 3.7), + ('3. IoU(0.95, 0.90)=0.82 > 0.5 → USUŃ', 2.9), + ('4. IoU(0.95, 0.85)=0.75 > 0.5 → USUŃ', 2.1), + ('5. IoU(0.95, 0.80)=0.10 < 0.5 → ZACHOWAJ', 1.3), + ] + colors = [GRAY4, GRAY2, GRAY4, GRAY4, GRAY2] + for (text, yp), c in zip(steps, colors): + ax.text(3.0, yp, text, ha='center', fontsize=FS, + bbox=dict(boxstyle='round,pad=0.2', facecolor=c, edgecolor=GRAY3)) + + ax.set_title('② Algorytm NMS\n(próg IoU = 0.5)', fontsize=FS, fontweight='bold') + + # After NMS + ax = axes[2] + ax.add_patch(mpatches.Rectangle((0, 0), 6, 5, facecolor=GRAY4, + edgecolor=LN, lw=1)) + # Only best box for each object + ax.add_patch(mpatches.Rectangle((1, 1), 2.5, 3, facecolor='none', + edgecolor=LN, lw=2.5)) + ax.text(2.25, 4.2, 'conf=0.95 ✓', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.add_patch(mpatches.Rectangle((4, 2), 1.5, 1.5, facecolor='none', + edgecolor=LN, lw=2.5)) + ax.text(4.75, 3.7, 'conf=0.80 ✓', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(3, 0.2, '✓ 2 unikalne obiekty', ha='center', fontsize=FS_SMALL, + fontweight='bold') + ax.set_xlim(-0.3, 6.3) + ax.set_ylim(-0.3, 5.3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('③ Po NMS\n(1 bbox na obiekt)', fontsize=FS, fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_nms_steps.png') + + +# ============================================================ +# 10. Detector from Classifier — 3 approaches +# ============================================================ +def draw_detector_from_classifier(): + fig, ax = plt.subplots(figsize=(11, 9)) + ax.set_xlim(-0.5, 11) + ax.set_ylim(-1, 9.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Jak zbudować detektor z klasyfikatora? — 3 podejścia', + fontsize=FS_TITLE, fontweight='bold', pad=12) + + # ---- Approach 1: Sliding Window ---- + y = 7.0 + ax.text(0, y + 1.5, '① Sliding Window (NAJWOLNIEJSZE)', fontsize=FS_LABEL, + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + # Image with sliding window + ax.add_patch(mpatches.Rectangle((0, y - 0.6), 1.8, 1.8, facecolor=GRAY1, + edgecolor=LN, lw=1.5)) + ax.text(0.9, y + 0.3, 'obraz', ha='center', fontsize=FS_SMALL) + # Sliding windows + for dx, dy in [(0.1, 0.1), (0.4, 0.1), (0.7, 0.1), (0.1, 0.5), (0.4, 0.5)]: + ax.add_patch(mpatches.Rectangle((dx, y - 0.5 + dy), 0.5, 0.5, + facecolor='none', edgecolor=LN, lw=0.8, linestyle='--')) + + draw_arrow(ax, 2.0, y + 0.3, 2.7, y + 0.3, lw=1.2) + ax.text(2.35, y + 0.6, '×miliony', fontsize=FS_SMALL, style='italic') + + draw_box(ax, 2.8, y - 0.3, 1.8, 1.2, 'Klasyfikator\n(ResNet)\n"kot? pies? tło?"', + fill=GRAY4, fontsize=FS) + draw_arrow(ax, 4.7, y + 0.3, 5.3, y + 0.3, lw=1.2) + draw_box(ax, 5.4, y - 0.3, 1.2, 1.2, 'NMS', fill=GRAY1, fontsize=FS) + draw_arrow(ax, 6.7, y + 0.3, 7.3, y + 0.3, lw=1.2) + ax.text(8.5, y + 0.3, '~3.3h / obraz!\n⚠ NIEPRAKTYCZNE', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + # ---- Approach 2: Region Proposals ---- + y = 3.8 + ax.text(0, y + 1.5, '② Region Proposals + Klasyfikator (= R-CNN)', fontsize=FS_LABEL, + fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.add_patch(mpatches.Rectangle((0, y - 0.6), 1.8, 1.8, facecolor=GRAY1, + edgecolor=LN, lw=1.5)) + ax.text(0.9, y + 0.3, 'obraz', ha='center', fontsize=FS_SMALL) + # A few smart regions + ax.add_patch(mpatches.Rectangle((0.1, y - 0.4), 0.7, 0.9, facecolor='none', + edgecolor=LN, lw=1.5)) + ax.add_patch(mpatches.Rectangle((0.9, y + 0.0), 0.7, 0.6, facecolor='none', + edgecolor=LN, lw=1.5)) + + draw_arrow(ax, 2.0, y + 0.3, 2.7, y + 0.3, lw=1.2) + draw_box(ax, 2.8, y - 0.3, 1.6, 1.2, 'Selective\nSearch\n~2000 regionów', + fill=GRAY2, fontsize=FS) + draw_arrow(ax, 4.5, y + 0.3, 5.1, y + 0.3, lw=1.2) + ax.text(4.8, y + 0.6, '×2000', fontsize=FS_SMALL, style='italic') + draw_box(ax, 5.2, y - 0.3, 1.5, 1.2, 'Klasyfikator\n(CNN)', fill=GRAY4, fontsize=FS) + draw_arrow(ax, 6.8, y + 0.3, 7.4, y + 0.3, lw=1.2) + draw_box(ax, 7.5, y - 0.3, 1.0, 1.2, 'NMS', fill=GRAY1, fontsize=FS) + draw_arrow(ax, 8.6, y + 0.3, 9.0, y + 0.3, lw=1.2) + ax.text(10.0, y + 0.3, '~20-50 s/obraz\n(250× szybciej)', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + # ---- Approach 3: Fine-tune backbone ---- + y = 0.5 + ax.text(0, y + 1.5, '③ Fine-tune backbone + detection head (NAJLEPSZE)', + fontsize=FS_LABEL, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY2, edgecolor=GRAY3)) + + ax.add_patch(mpatches.Rectangle((0, y - 0.6), 1.8, 1.8, facecolor=GRAY1, + edgecolor=LN, lw=1.5)) + ax.text(0.9, y + 0.3, 'obraz', ha='center', fontsize=FS_SMALL) + + draw_arrow(ax, 2.0, y + 0.3, 2.7, y + 0.3, lw=1.2) + draw_box(ax, 2.8, y - 0.3, 1.8, 1.2, 'Pretrained\nbackbone\n(ResNet)', + fill=GRAY3, fontsize=FS, fontweight='bold') + draw_arrow(ax, 4.7, y + 0.3, 5.3, y + 0.3, lw=1.2) + + # Two heads from feature map + draw_box(ax, 5.4, y + 0.3, 1.6, 0.6, 'cls head\nP(klasa)', fill=GRAY4, fontsize=FS) + draw_box(ax, 5.4, y - 0.5, 1.6, 0.6, 'bbox head\nΔx,Δy,Δw,Δh', fill=GRAY4, fontsize=FS) + + draw_arrow(ax, 7.1, y + 0.6, 7.7, y + 0.6, lw=1.0) + draw_arrow(ax, 7.1, y - 0.2, 7.7, y - 0.2, lw=1.0) + draw_box(ax, 7.8, y - 0.3, 1.0, 1.2, 'NMS', fill=GRAY1, fontsize=FS) + + draw_arrow(ax, 8.9, y + 0.3, 9.3, y + 0.3, lw=1.2) + ax.text(10.2, y + 0.3, '5-155 fps!\n✓ NAJLEPSZE', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY2, edgecolor=GRAY3)) + + save_fig(fig, 'q24_detector_from_classifier.png') + + +# ============================================================ +# 11. SVM Hyperplane +# ============================================================ +def draw_svm_hyperplane(): + fig, ax = plt.subplots(figsize=(6, 5)) + ax.set_title('SVM — hiperpłaszczyzna i margines', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + np.random.seed(42) + # Class +1 (top-right) + x_pos = np.random.randn(15) * 0.5 + 3 + y_pos = np.random.randn(15) * 0.5 + 3 + ax.scatter(x_pos, y_pos, marker='o', s=50, facecolors='white', + edgecolors=LN, linewidths=1.5, label='klasa +1 (pieszy)', zorder=3) + + # Class -1 (bottom-left) + x_neg = np.random.randn(15) * 0.5 + 1 + y_neg = np.random.randn(15) * 0.5 + 1 + ax.scatter(x_neg, y_neg, marker='x', s=50, c=LN, linewidths=1.5, + label='klasa -1 (tło)', zorder=3) + + # Hyperplane (decision boundary) + x_line = np.linspace(-0.5, 5, 100) + y_line = -x_line + 4.0 + ax.plot(x_line, y_line, 'k-', lw=2, label='hiperpłaszczyzna') + + # Margin lines + ax.plot(x_line, y_line + 0.7, 'k--', lw=1, alpha=0.5) + ax.plot(x_line, y_line - 0.7, 'k--', lw=1, alpha=0.5) + + # Margin annotation + ax.annotate('', xy=(2.5, 1.5 + 0.7), xytext=(2.5, 1.5 - 0.7), + arrowprops=dict(arrowstyle='<->', color=LN, lw=1.5)) + ax.text(2.8, 1.5, 'margines\n(MAX!)', fontsize=FS, fontweight='bold') + + # Support vectors (highlight closest points) + # Find points closest to the line + ax.scatter([2.5], [2.2], marker='o', s=120, facecolors='none', + edgecolors=LN, linewidths=2.5, zorder=4) + ax.scatter([1.5], [1.8], marker='x', s=120, c=LN, linewidths=2.5, zorder=4) + ax.annotate('support\nvectors', xy=(1.5, 1.8), xytext=(0.2, 3.0), + fontsize=FS, fontweight='bold', + arrowprops=dict(arrowstyle='->', color=LN, lw=1)) + + ax.set_xlim(-0.5, 5) + ax.set_ylim(-0.5, 5) + ax.set_xlabel('cecha 1 (np. gradient pionowy)', fontsize=FS) + ax.set_ylabel('cecha 2 (np. gradient poziomy)', fontsize=FS) + ax.legend(fontsize=FS_SMALL, loc='lower right') + ax.set_aspect('equal') + + save_fig(fig, 'q24_svm_hyperplane.png') + + +# ============================================================ +# 12. Two-stage vs One-stage comparison table +# ============================================================ +def draw_two_vs_one_stage(): + fig, ax = plt.subplots(figsize=(10, 3.5)) + ax.set_xlim(0, 10) + ax.set_ylim(-0.5, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Two-stage vs One-stage — porównanie', fontsize=FS_TITLE, + fontweight='bold', pad=8) + + headers = ['Cecha', 'Two-stage\n(Faster R-CNN)', 'One-stage\n(YOLO)'] + rows = [ + ['Szybkość', '~5 fps', '45-155 fps'], + ['Dokładność (mAP)', 'wyższa (historycznie)', 'dorównuje (YOLOv8)'], + ['Małe obiekty', 'lepszy', 'gorszy (SSD/FPN pomaga)'], + ['Architektura', '2 etapy + NMS', '1 etap + NMS'], + ['Real-time?', 'NIE', 'TAK'], + ] + col_widths = [2.5, 3.5, 3.5] + draw_table(ax, headers, rows, 0.2, 3.8, col_widths, row_h=0.65, + fontsize=FS, header_fontsize=FS) + + save_fig(fig, 'q24_two_vs_one_stage.png') + + +# ============================================================ +# 13. ROI Pooling illustration +# ============================================================ +def draw_roi_pooling(): + fig, axes = plt.subplots(1, 3, figsize=(12, 4)) + fig.suptitle('ROI Pooling — dowolny rozmiar → stały rozmiar', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Feature map with ROI + ax = axes[0] + # Draw feature map grid + fm = np.random.randint(0, 10, (8, 8)) + ax.imshow(fm, cmap='gray', vmin=0, vmax=10, alpha=0.3) + for i in range(9): + ax.axhline(y=i-0.5, color=LN, lw=0.3) + ax.axvline(x=i-0.5, color=LN, lw=0.3) + # ROI rectangle + ax.add_patch(mpatches.Rectangle((1.5, 1.5), 4, 4, facecolor='none', + edgecolor=LN, lw=3, linestyle='-')) + ax.text(3.5, 0.8, 'ROI', ha='center', fontsize=FS_LABEL, fontweight='bold') + ax.set_xlim(-0.5, 7.5) + ax.set_ylim(7.5, -0.5) + ax.set_title('① Feature map\nz zaznaczonym ROI', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # ROI divided into grid + ax = axes[1] + roi_data = np.array([ + [1, 3, 2, 1], + [0, 5, 1, 6], + [0, 4, 1, 0], + [7, 2, 9, 1], + ]) + ax.imshow(roi_data, cmap='gray', vmin=0, vmax=10) + for i in range(5): + ax.axhline(y=i-0.5, color=LN, lw=1) + ax.axvline(x=i-0.5, color=LN, lw=1) + # Grid lines for 2x2 pooling + ax.axhline(y=0.5, color=LN, lw=3, linestyle='--') + ax.axvline(x=0.5, color=LN, lw=3, linestyle='--') + for i in range(4): + for j in range(4): + ax.text(j, i, str(roi_data[i, j]), ha='center', va='center', + fontsize=10, fontweight='bold', + color='white' if roi_data[i, j] > 5 else 'black') + ax.set_title('② ROI podzielony\nna siatkę 2×2', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + # Output after pooling + ax = axes[2] + out = np.array([[5, 6], [7, 9]]) + ax.imshow(out, cmap='gray', vmin=0, vmax=10) + for i in range(3): + ax.axhline(y=i-0.5, color=LN, lw=1.5) + ax.axvline(x=i-0.5, color=LN, lw=1.5) + for i in range(2): + for j in range(2): + ax.text(j, i, str(out[i, j]), ha='center', va='center', + fontsize=14, fontweight='bold', + color='white' if out[i, j] > 5 else 'black') + ax.set_title('③ Po ROI Pool 2×2\n(max z każdej komórki)', fontsize=FS, fontweight='bold') + ax.set_xticks([]) + ax.set_yticks([]) + + fig.tight_layout() + save_fig(fig, 'q24_roi_pooling.png') + + +# ============================================================ +# 14. DETR Pipeline +# ============================================================ +def draw_detr_pipeline(): + fig, ax = plt.subplots(figsize=(11, 4.5)) + ax.set_xlim(-0.5, 11.5) + ax.set_ylim(-1, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('DETR — Transformer do detekcji (bez NMS, bez anchorów)', + fontsize=FS_TITLE, fontweight='bold', pad=12) + + # Pipeline + draw_box(ax, 0, 1.5, 1.5, 1.5, 'Obraz\nwejściowy', fill=GRAY1, fontsize=FS) + draw_arrow(ax, 1.6, 2.25, 2.1, 2.25, lw=1.5) + + draw_box(ax, 2.2, 1.5, 1.5, 1.5, 'CNN\nBackbone\n(ResNet)', fill=GRAY3, + fontsize=FS, fontweight='bold') + draw_arrow(ax, 3.8, 2.25, 4.3, 2.25, lw=1.5) + + draw_box(ax, 4.4, 1.5, 1.8, 1.5, 'Transformer\nEncoder\n(self-attention)', + fill=GRAY2, fontsize=FS) + draw_arrow(ax, 6.3, 2.25, 6.8, 2.25, lw=1.5) + + draw_box(ax, 6.9, 1.5, 1.8, 1.5, 'Transformer\nDecoder\n(N=100 queries)', + fill=GRAY2, fontsize=FS, fontweight='bold') + + # Output branches + draw_arrow(ax, 8.8, 2.5, 9.5, 3.0, lw=1.2) + draw_box(ax, 9.6, 2.7, 1.5, 0.7, 'klasa₁...klasa₁₀₀', fill=GRAY4, fontsize=FS_SMALL) + + draw_arrow(ax, 8.8, 2.0, 9.5, 1.5, lw=1.2) + draw_box(ax, 9.6, 1.2, 1.5, 0.7, 'bbox₁...bbox₁₀₀', fill=GRAY4, fontsize=FS_SMALL) + + # Annotations + ax.text(7.8, 0.5, '100 object queries → 5 obiektów + 95× "brak"', + ha='center', fontsize=FS, style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.text(5.5, 0.0, 'Hungarian matching (trening): optymalne dopasowanie predykcji do GT', + ha='center', fontsize=FS_SMALL, style='italic', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, edgecolor=GRAY5)) + + # Big benefit box + ax.text(5.5, 4.0, 'BEZ anchorów • BEZ NMS • end-to-end • prosty pipeline', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY2, edgecolor=GRAY3)) + + save_fig(fig, 'q24_detr_pipeline.png') + + +# ============================================================ +# 15. Sliding Window illustration +# ============================================================ +def draw_sliding_window(): + fig, axes = plt.subplots(1, 3, figsize=(12, 4)) + fig.suptitle('Sliding Window — najprostsze podejście do detekcji', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Multi-position + ax = axes[0] + ax.add_patch(mpatches.Rectangle((0, 0), 8, 6, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + # Grid of sliding windows + for i in range(4): + for j in range(3): + ax.add_patch(mpatches.Rectangle((i*1.8 + 0.2, j*1.8 + 0.2), 1.5, 1.5, + facecolor='none', edgecolor=LN, lw=0.6, linestyle='--')) + # Highlight current window + ax.add_patch(mpatches.Rectangle((2.0, 2.0), 1.5, 1.5, facecolor='none', + edgecolor=LN, lw=2.5)) + ax.set_xlim(-0.5, 8.5) + ax.set_ylim(-0.5, 6.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('① Wiele pozycji\n(krok co 8 px)', fontsize=FS, fontweight='bold') + + # Multi-scale + ax = axes[1] + ax.add_patch(mpatches.Rectangle((0, 0), 6, 5, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + sizes = [(0.8, 0.8), (1.5, 1.5), (2.5, 2.5), (3.5, 3.5)] + for i, (w, h) in enumerate(sizes): + ax.add_patch(mpatches.Rectangle((0.3 + i*0.3, 0.3 + i*0.3), w, h, + facecolor='none', edgecolor=LN, lw=1 + i*0.3, + linestyle=[':', '--', '-.', '-'][i])) + ax.text(3, 0, '4+ skal', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.set_xlim(-0.5, 6.5) + ax.set_ylim(-0.5, 5.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('② Wiele skal\n(obiekty mają różne rozmiary)', fontsize=FS, + fontweight='bold') + + # Count + ax = axes[2] + ax.axis('off') + ax.set_xlim(0, 6) + ax.set_ylim(0, 5) + + lines = [ + ('Obraz: 640 × 480 px', 4.5), + ('Okno: 64 × 64 px, krok 8 px', 3.8), + ('Pozycje: ~72 × 52 = 3 744', 3.1), + ('× 5 skal = 18 720 okien', 2.4), + ('× klasyfikacja = WOLNE!', 1.7), + ('→ ~3h na jeden obraz', 0.8), + ] + for text, yp in lines: + fw = 'bold' if '~3h' in text or 'WOLNE' in text else 'normal' + col = GRAY2 if 'WOLNE' in text or '~3h' in text else GRAY4 + ax.text(3.0, yp, text, ha='center', fontsize=FS, + fontweight=fw, + bbox=dict(boxstyle='round,pad=0.2', facecolor=col, edgecolor=GRAY3)) + + ax.set_title('③ Dlaczego wolne?\n(miliony klasyfikacji)', fontsize=FS, fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_sliding_window.png') + + +# ============================================================ +# 16. FPN (Feature Pyramid Network) +# ============================================================ +def draw_fpn(): + fig, ax = plt.subplots(figsize=(9, 5)) + ax.set_xlim(-0.5, 9.5) + ax.set_ylim(-0.5, 5.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('FPN (Feature Pyramid Network) — detekcja obiektów wszystkich rozmiarów', + fontsize=FS_TITLE, fontweight='bold', pad=12) + + # Bottom-up (backbone) + levels = [ + (0, 0, 2.0, 2.0, 'C2\n56×56', 'duże\ndetale'), + (0, 2.2, 1.5, 1.5, 'C3\n28×28', ''), + (0, 3.9, 1.0, 1.0, 'C4\n14×14', ''), + (0, 5.1, 0.6, 0.6, 'C5\n7×7', 'kontekst'), + ] + + for x, y, w, h, label, note in levels: + ax.add_patch(mpatches.Rectangle((x, y - h), w, h, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + ax.text(x + w/2, y - h/2, label, ha='center', va='center', + fontsize=FS_SMALL, fontweight='bold') + if note: + ax.text(x + w + 0.15, y - h/2, note, ha='left', va='center', + fontsize=5, style='italic') + + ax.text(1.0, -0.3, 'Bottom-up\n(backbone)', ha='center', fontsize=FS, + fontweight='bold') + + # Top-down + lateral + td_levels = [ + (4.5, 5.1, 0.6, 0.6, 'P5'), + (4.5, 3.9, 1.0, 1.0, 'P4'), + (4.5, 2.2, 1.5, 1.5, 'P3'), + (4.5, 0, 2.0, 2.0, 'P2'), + ] + + for x, y, w, h, label in td_levels: + ax.add_patch(mpatches.Rectangle((x, y - h + h), w, h, facecolor=GRAY2, + edgecolor=LN, lw=1.5)) + ax.text(x + w/2, y - h/2 + h, label, ha='center', va='center', + fontsize=FS_SMALL, fontweight='bold') + + # Lateral connections + for (_, y1, w1, h1, _, _), (x2, y2, w2, h2, _) in zip(levels, td_levels): + draw_arrow(ax, w1 + 0.2, y1 - h1/2, x2 - 0.1, y2 + h2/2, lw=1, style='->') + + # Top-down arrows + for i in range(len(td_levels) - 1): + x2, y2, w2, h2, _ = td_levels[i] + x3, y3, w3, h3, _ = td_levels[i + 1] + draw_arrow(ax, x2 + w2/2, y2, x3 + w3/2, y3 + h3 + 0.1, lw=1.2, + style='->', color=GRAY3) + + ax.text(5.5, -0.3, 'Top-down + lateral\n(FPN)', ha='center', fontsize=FS, + fontweight='bold') + + # Detection outputs + det_labels = ['małe obj.', 'średnie', 'duże', 'b. duże'] + for i, (x, y, w, h, label) in enumerate(td_levels): + draw_arrow(ax, x + w + 0.1, y + h/2, 7.5, y + h/2, lw=0.8) + ax.text(7.7, y + h/2, f'detekcja:\n{det_labels[3-i]}', fontsize=FS_SMALL, + va='center') + + save_fig(fig, 'q24_fpn.png') + + +# ============================================================ +# 17. Anchor boxes +# ============================================================ +def draw_anchor_boxes(): + fig, ax = plt.subplots(figsize=(7, 5)) + ax.set_title('Anchor boxes — predefiniowane kształty', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + ax.add_patch(mpatches.Rectangle((0, 0), 6, 5, facecolor=GRAY4, + edgecolor=LN, lw=1)) + + # Center point + cx, cy = 3, 2.5 + ax.plot(cx, cy, 'ko', markersize=8, zorder=5) + ax.text(cx + 0.15, cy + 0.15, '(x, y)', fontsize=FS, fontweight='bold') + + # 9 anchors: 3 sizes × 3 ratios + anchors = [ + # (w, h, style, label) + (0.8, 0.8, '-', '1:1 small'), + (1.6, 1.6, '-', '1:1 medium'), + (2.4, 2.4, '-', '1:1 large'), + (0.6, 1.2, '--', '1:2 small'), + (1.2, 2.4, '--', '1:2 medium'), + (1.8, 3.6, '--', '1:2 large'), + (1.2, 0.6, ':', '2:1 small'), + (2.4, 1.2, ':', '2:1 medium'), + (3.6, 1.8, ':', '2:1 large'), + ] + + drawn = [] + for w, h, ls, label in anchors: + rect = mpatches.Rectangle((cx - w/2, cy - h/2), w, h, + facecolor='none', edgecolor=LN, + lw=1.2, linestyle=ls) + ax.add_patch(rect) + + # Legend-style labels + ax.text(3, -0.5, '9 anchorów = 3 rozmiary × 3 proporcje (1:1, 1:2, 2:1)\n' + 'Sieć predykuje PRZESUNIĘCIE od najbliższego anchora', + ha='center', fontsize=FS, style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.set_xlim(-0.5, 6.5) + ax.set_ylim(-1.2, 5.5) + ax.set_aspect('equal') + ax.axis('off') + + save_fig(fig, 'q24_anchor_boxes.png') + + +# ============================================================ +# 18. Detection task comparison +# ============================================================ +def draw_detection_tasks(): + fig, axes = plt.subplots(1, 3, figsize=(12, 4)) + fig.suptitle('Klasyfikacja vs Detekcja vs Segmentacja', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Classification + ax = axes[0] + ax.add_patch(mpatches.Rectangle((0, 0), 4, 4, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + # Simple cat silhouette + ax.add_patch(mpatches.Ellipse((2, 2), 2, 1.5, facecolor=GRAY3, + edgecolor=LN, lw=1)) + ax.add_patch(mpatches.Ellipse((2, 3), 1, 0.8, facecolor=GRAY3, + edgecolor=LN, lw=1)) + # Ears + ax.plot([1.6, 1.5, 1.8], [3.3, 3.8, 3.4], color=LN, lw=1.5) + ax.plot([2.2, 2.5, 2.4], [3.3, 3.8, 3.4], color=LN, lw=1.5) + ax.text(2, -0.4, '→ "KOT" (jedna etykieta)', ha='center', + fontsize=FS, fontweight='bold') + ax.set_xlim(-0.5, 4.5) + ax.set_ylim(-0.8, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Klasyfikacja\n(co?)', fontsize=FS, fontweight='bold') + + # Detection + ax = axes[1] + ax.add_patch(mpatches.Rectangle((0, 0), 4, 4, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + # Cat + ax.add_patch(mpatches.Ellipse((1.2, 2), 1.2, 1, facecolor=GRAY3, + edgecolor=LN, lw=1)) + ax.add_patch(mpatches.Ellipse((1.2, 2.8), 0.7, 0.5, facecolor=GRAY3, + edgecolor=LN, lw=1)) + # Dog + ax.add_patch(mpatches.Ellipse((3, 1.5), 1.2, 1, facecolor=GRAY2, + edgecolor=LN, lw=1)) + ax.add_patch(mpatches.Ellipse((3, 2.3), 0.7, 0.5, facecolor=GRAY2, + edgecolor=LN, lw=1)) + # Bounding boxes + ax.add_patch(mpatches.Rectangle((0.3, 1.2), 1.8, 2.2, facecolor='none', + edgecolor=LN, lw=2.5)) + ax.text(1.2, 3.5, 'KOT', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.add_patch(mpatches.Rectangle((2.1, 0.8), 1.7, 2.0, facecolor='none', + edgecolor=LN, lw=2.5)) + ax.text(3.0, 2.9, 'PIES', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(2, -0.4, '→ bbox + klasa (N obiektów)', ha='center', + fontsize=FS, fontweight='bold') + ax.set_xlim(-0.5, 4.5) + ax.set_ylim(-0.8, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Detekcja\n(co? + gdzie?)', fontsize=FS, fontweight='bold') + + # Segmentation + ax = axes[2] + ax.add_patch(mpatches.Rectangle((0, 0), 4, 4, facecolor=GRAY4, + edgecolor=LN, lw=1.5)) + # Cat mask (detailed) + theta = np.linspace(0, 2*np.pi, 30) + cat_x = 1.2 + 0.6*np.cos(theta) + 0.1*np.sin(3*theta) + cat_y = 2 + 0.5*np.sin(theta) + 0.1*np.cos(2*theta) + ax.fill(cat_x, cat_y, facecolor=GRAY3, edgecolor=LN, lw=1.5) + # Dog mask + dog_x = 3.0 + 0.6*np.cos(theta) + 0.05*np.sin(4*theta) + dog_y = 1.5 + 0.5*np.sin(theta) + 0.08*np.cos(3*theta) + ax.fill(dog_x, dog_y, facecolor=GRAY2, edgecolor=LN, lw=1.5) + ax.text(1.2, 2, 'KOT', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(3.0, 1.5, 'PIES', ha='center', fontsize=FS_SMALL, fontweight='bold') + ax.text(2, -0.4, '→ maska pikseli (per piksel)', ha='center', + fontsize=FS, fontweight='bold') + ax.set_xlim(-0.5, 4.5) + ax.set_ylim(-0.8, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Segmentacja\n(dokładna maska)', fontsize=FS, fontweight='bold') + + fig.tight_layout() + save_fig(fig, 'q24_detection_tasks.png') + + +# ============================================================ +# 19. CNN Architecture overview +# ============================================================ +def draw_cnn_architecture(): + fig, ax = plt.subplots(figsize=(12, 4)) + ax.set_xlim(-0.5, 12.5) + ax.set_ylim(-1, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('CNN — od obrazu do predykcji (architektura)', fontsize=FS_TITLE, + fontweight='bold', pad=12) + + # Input image + draw_box(ax, 0, 0.5, 1.5, 3, 'Obraz\n224×224×3', fill=GRAY1, fontsize=FS) + + # Conv1 + draw_arrow(ax, 1.6, 2.0, 2.1, 2.0, lw=1.2) + draw_box(ax, 2.2, 0.8, 1.2, 2.4, 'Conv1\n+ReLU\n55×55×96', fill=GRAY4, fontsize=FS_SMALL) + + # Pool1 + draw_arrow(ax, 3.5, 2.0, 3.9, 2.0, lw=1.2) + draw_box(ax, 4.0, 1.0, 1.0, 2.0, 'Pool\n27×27\n×96', fill=GRAY2, fontsize=FS_SMALL) + + # Conv2 + draw_arrow(ax, 5.1, 2.0, 5.5, 2.0, lw=1.2) + draw_box(ax, 5.6, 0.8, 1.2, 2.4, 'Conv2\n+ReLU\n27×27\n×256', fill=GRAY4, fontsize=FS_SMALL) + + # Pool2 + draw_arrow(ax, 6.9, 2.0, 7.3, 2.0, lw=1.2) + draw_box(ax, 7.4, 1.2, 0.8, 1.6, 'Pool\n13×13\n×256', fill=GRAY2, fontsize=FS_SMALL) + + # More conv... + draw_arrow(ax, 8.3, 2.0, 8.7, 2.0, lw=1.2) + ax.text(9.0, 2.0, '...', fontsize=14, ha='center', va='center') + draw_arrow(ax, 9.3, 2.0, 9.7, 2.0, lw=1.2) + + # FC + draw_box(ax, 9.8, 1.2, 1.0, 1.6, 'FC\n4096', fill=GRAY3, fontsize=FS) + + draw_arrow(ax, 10.9, 2.0, 11.3, 2.0, lw=1.2) + + # Output + draw_box(ax, 11.4, 1.5, 1.0, 1.0, 'Softmax\n1000 klas', fill=GRAY1, fontsize=FS_SMALL) + + # Annotations below + ax.text(3.0, 0.0, 'rozmiar maleje\n224→55→27→13→6', ha='center', fontsize=FS_SMALL, + style='italic') + ax.text(6.0, 0.0, 'kanały rosną\n3→96→256→384', ha='center', fontsize=FS_SMALL, + style='italic') + ax.text(10.0, 0.0, 'decyzja\nkońcowa', ha='center', fontsize=FS_SMALL, + style='italic') + + # hierarchy + ax.text(6.0, 4.0, 'Hierarchia: krawędzie → rogi → fragmenty → obiekty (K-R-F-O)', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + save_fig(fig, 'q24_cnn_architecture.png') + + +# ============================================================ +# MAIN +# ============================================================ +if __name__ == '__main__': + print("Generating PYTANIE 24 diagrams...") + draw_hog_svm_pipeline() + draw_hog_gradient_steps() + draw_viola_jones_cascade() + draw_haar_features() + draw_integral_image() + draw_rcnn_evolution() + draw_yolo_grid() + draw_iou_diagram() + draw_nms_steps() + draw_detector_from_classifier() + draw_svm_hyperplane() + draw_two_vs_one_stage() + draw_roi_pooling() + draw_detr_pipeline() + draw_sliding_window() + draw_fpn() + draw_anchor_boxes() + draw_detection_tasks() + draw_cnn_architecture() + print("\nAll PYTANIE 24 diagrams generated!") diff --git a/pytania/generate_q31_diagrams.py b/pytania/generate_q31_diagrams.py new file mode 100644 index 0000000..4fd7310 --- /dev/null +++ b/pytania/generate_q31_diagrams.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python3 +""" +Generate diagrams for PYTANIE 31: Interaktywne wspomaganie decyzji w warunkach ryzyka. + +Diagrams: + 1. Payoff matrix + all criteria results comparison (bar chart) + 2. Regret matrix construction step-by-step + 3. Hurwicz α interpolation between maximax and maximin + 4. Decision criteria mnemonic map + 5. Expected value criterion with probability-weighted bars + 6. Decision conditions spectrum (pewność → ryzyko → niepewność) + +All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=LN, facecolor=fill) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +# ============================================================ +# 1. PAYOFF MATRIX + ALL CRITERIA BAR CHART +# ============================================================ +def draw_criteria_comparison(): + fig, axes = plt.subplots(1, 2, figsize=(8.27, 4.5), gridspec_kw={'width_ratios': [1.2, 1]}) + + # -- Left: Payoff matrix as styled table -- + ax = axes[0] + ax.axis('off') + ax.set_xlim(0, 6) + ax.set_ylim(0, 6) + ax.set_title('Macierz wypłat (tys. zł)', fontsize=FS_TITLE, fontweight='bold', pad=8) + + # Headers + headers_col = ['', 'S₁\n(dobra)', 'S₂\n(średnia)', 'S₃\n(zła)'] + rows = [ + ['A₁ (fabryka)', '200', '50', '−100'], + ['A₂ (sklep)', '80', '70', '40'], + ['A₃ (obligacje)', '30', '30', '30'], + ] + + col_w = [1.8, 1.2, 1.2, 1.2] + row_h = 0.7 + start_y = 4.5 + start_x = 0.2 + + # Draw header row + x = start_x + for j, h in enumerate(headers_col): + fill = GRAY2 if j > 0 else GRAY3 + rect = mpatches.Rectangle((x, start_y), col_w[j], row_h, lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + col_w[j]/2, start_y + row_h/2, h, ha='center', va='center', + fontsize=FS, fontweight='bold') + x += col_w[j] + + # Draw data rows + for i, row in enumerate(rows): + x = start_x + y = start_y - (i+1) * row_h + for j, val in enumerate(row): + fill = GRAY4 if j == 0 else ('white' if i % 2 == 0 else GRAY1) + # Highlight negative + if val.startswith('−'): + fill = '#D8D8D8' + rect = mpatches.Rectangle((x, y), col_w[j], row_h, lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + fw = 'bold' if j == 0 else 'normal' + ax.text(x + col_w[j]/2, y + row_h/2, val, ha='center', va='center', + fontsize=FS, fontweight=fw) + x += col_w[j] + + # Probability row for EV + x = start_x + y = start_y - 4 * row_h + probs = ['p (dla E[X]):', '0.5', '0.3', '0.2'] + for j, val in enumerate(probs): + fill = GRAY5 if j > 0 else GRAY3 + rect = mpatches.Rectangle((x, y), col_w[j], row_h * 0.7, lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + col_w[j]/2, y + row_h*0.35, val, ha='center', va='center', + fontsize=7, fontweight='bold', style='italic') + x += col_w[j] + + # -- Right: Bar chart comparing criteria results -- + ax2 = axes[1] + criteria = ['E[X]', 'Laplace', 'Maximax', 'Maximin', 'Hurwicz\nα=0.6', 'Savage'] + + # Recalculate with probabilities 0.5, 0.3, 0.2 + # E[X]: A1=200*0.5+50*0.3+(-100)*0.2=100+15-20=95 + # A2=80*0.5+70*0.3+40*0.2=40+21+8=69 + # A3=30*0.5+30*0.3+30*0.2=15+9+6=30 + ev = [95, 69, 30] + laplace = [50, 63.3, 30] + maximax = [200, 80, 30] + maximin = [-100, 40, 30] + hurwicz = [80, 64, 30] # α=0.6 + savage_maxregret = [140, 120, 170] # lower = better + + # Which alternative wins for each criterion? + winners = [0, 1, 0, 1, 0, 1] # index of winning alternative + winner_vals = [95, 63.3, 200, 40, 80, 120] + + # Display as grouped bar chart - each criterion shows the 3 alternatives + x_pos = np.arange(len(criteria)) + width = 0.22 + hatches = ['///', '...', 'xxx'] + labels = ['A₁ (fabryka)', 'A₂ (sklep)', 'A₃ (obligacje)'] + + all_vals = [ + [ev[0], laplace[0], maximax[0], maximin[0], hurwicz[0], savage_maxregret[0]], + [ev[1], laplace[1], maximax[1], maximin[1], hurwicz[1], savage_maxregret[1]], + [ev[2], laplace[2], maximax[2], maximin[2], hurwicz[2], savage_maxregret[2]], + ] + + for i in range(3): + bars = ax2.bar(x_pos + (i - 1) * width, all_vals[i], width, + label=labels[i], color='white', edgecolor=LN, hatch=hatches[i], lw=0.8) + + # Mark winners with star + for c_idx in range(len(criteria)): + w = winners[c_idx] + val = all_vals[w][c_idx] + ax2.text(x_pos[c_idx] + (w - 1) * width, val + 5, '★', + ha='center', va='bottom', fontsize=10, fontweight='bold') + + ax2.set_xticks(x_pos) + ax2.set_xticklabels(criteria, fontsize=7) + ax2.set_ylabel('Wartość kryterium', fontsize=8) + ax2.set_title('Porównanie kryteriów', fontsize=FS_TITLE, fontweight='bold', pad=8) + ax2.legend(fontsize=7, loc='upper right') + ax2.axhline(y=0, color=LN, lw=0.5, ls='-') + ax2.spines['top'].set_visible(False) + ax2.spines['right'].set_visible(False) + ax2.tick_params(labelsize=7) + + # Note about Savage + ax2.text(5, -30, '(Savage: niżej\n= lepiej)', fontsize=6, ha='center', + va='top', style='italic') + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_criteria_comparison.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# 2. REGRET MATRIX CONSTRUCTION +# ============================================================ +def draw_regret_matrix(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 5)) + ax.axis('off') + ax.set_xlim(0, 10) + ax.set_ylim(0, 7) + ax.set_title('Kryterium Savage\'a — budowa macierzy żalu', + fontsize=FS_TITLE + 1, fontweight='bold', pad=10) + + # --- Step 1: Original payoff matrix (left) --- + ax.text(2.2, 6.3, 'Krok 1: Macierz wypłat', fontsize=9, fontweight='bold', + ha='center', va='center') + + col_w = 1.0 + row_h = 0.55 + headers = ['', 'S₁', 'S₂', 'S₃'] + data = [ + ['A₁', '200', '50', '−100'], + ['A₂', '80', '70', '40'], + ['A₃', '30', '30', '30'], + ] + start_x = 0.3 + start_y = 5.5 + + for j, h in enumerate(headers): + w = 0.7 if j == 0 else col_w + x = start_x + (0 if j == 0 else 0.7 + (j-1)*col_w) + rect = mpatches.Rectangle((x, start_y), w, row_h, lw=1, edgecolor=LN, facecolor=GRAY2) + ax.add_patch(rect) + ax.text(x + w/2, start_y + row_h/2, h, ha='center', va='center', + fontsize=FS, fontweight='bold') + + for i, row in enumerate(data): + y = start_y - (i+1) * row_h + for j, val in enumerate(row): + w = 0.7 if j == 0 else col_w + x = start_x + (0 if j == 0 else 0.7 + (j-1)*col_w) + fill = GRAY4 if j == 0 else 'white' + rect = mpatches.Rectangle((x, y), w, row_h, lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + w/2, y + row_h/2, val, ha='center', va='center', fontsize=FS) + + # Max per column annotation + max_y = start_y - 3 * row_h - 0.1 + ax.text(start_x + 0.7 + 0.5*col_w, max_y, 'max=200', fontsize=7, + ha='center', va='top', fontweight='bold', color='#333') + ax.text(start_x + 0.7 + 1.5*col_w, max_y, 'max=70', fontsize=7, + ha='center', va='top', fontweight='bold', color='#333') + ax.text(start_x + 0.7 + 2.5*col_w, max_y, 'max=40', fontsize=7, + ha='center', va='top', fontweight='bold', color='#333') + + # Arrow + ax.annotate("", xy=(5.0, 4.8), xytext=(4.2, 4.8), + arrowprops=dict(arrowstyle='->', color=LN, lw=2)) + ax.text(4.6, 5.0, 'rᵢⱼ = max − aᵢⱼ', fontsize=8, ha='center', va='bottom', + fontweight='bold') + + # --- Step 2: Regret matrix (right) --- + ax.text(7.5, 6.3, 'Krok 2: Macierz żalu', fontsize=9, fontweight='bold', + ha='center', va='center') + + regret_data = [ + ['A₁', '0', '20', '140'], + ['A₂', '120', '0', '0'], + ['A₃', '170', '40', '10'], + ] + headers2 = ['', 'S₁', 'S₂', 'S₃', 'max rᵢ'] + start_x2 = 5.3 + + for j, h in enumerate(headers2): + w = 0.7 if j == 0 else (0.9 if j < 4 else 1.0) + x = start_x2 + if j == 0: + x = start_x2 + elif j <= 3: + x = start_x2 + 0.7 + (j-1)*0.9 + else: + x = start_x2 + 0.7 + 3*0.9 + rect = mpatches.Rectangle((x, start_y), w, row_h, lw=1, edgecolor=LN, + facecolor=GRAY2 if j < 4 else GRAY3) + ax.add_patch(rect) + ax.text(x + w/2, start_y + row_h/2, h, ha='center', va='center', + fontsize=FS, fontweight='bold') + + max_regrets = [140, 120, 170] + for i, row in enumerate(regret_data): + y = start_y - (i+1) * row_h + for j, val in enumerate(row): + w = 0.7 if j == 0 else 0.9 + x = start_x2 + (0 if j == 0 else 0.7 + (j-1)*0.9) + fill = GRAY4 if j == 0 else 'white' + # Highlight the max regret cell + if j > 0 and int(val) == max_regrets[i]: + fill = GRAY2 + rect = mpatches.Rectangle((x, y), w, row_h, lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + fw = 'bold' if (j > 0 and int(val) == max_regrets[i]) else 'normal' + ax.text(x + w/2, y + row_h/2, val, ha='center', va='center', + fontsize=FS, fontweight=fw) + + # Max regret column + x = start_x2 + 0.7 + 3*0.9 + w = 1.0 + fill = '#C8C8C8' if max_regrets[i] == min(max_regrets) else GRAY1 + rect = mpatches.Rectangle((x, y), w, row_h, lw=1.5 if max_regrets[i]==min(max_regrets) else 1, + edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + marker = ' ★' if max_regrets[i] == min(max_regrets) else '' + ax.text(x + w/2, y + row_h/2, f'{max_regrets[i]}{marker}', ha='center', va='center', + fontsize=FS, fontweight='bold') + + # Bottom conclusion + ax.text(5.0, 2.8, 'Krok 3: Wybierz min z max żalu → A₂ (max żal = 120)', + fontsize=10, ha='center', va='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY1, edgecolor=LN, lw=1.5)) + + # Interpretatation examples + ax.text(5.0, 2.0, 'Interpretacja żalu: r₁₃ = 140 oznacza:\n' + '„Gdyby nastąpił S₃ (zła koniunktura), a wybrałbym A₁,\n' + 'żałowałbym, bo najlepszą opcją byłoby A₂ z wynikiem 40 — traciłbym 140"', + fontsize=7.5, ha='center', va='center', style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8)) + + # Mnemonic + ax.text(5.0, 0.8, 'Mnemonik: Savage = „Żal jak nóż"\nMaksymalny żal to nóż ' + '— wybierz opcję z NAJMNIEJSZYM nożem', + fontsize=8, ha='center', va='center', fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor=LN, lw=1)) + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_regret_matrix.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# 3. HURWICZ α INTERPOLATION +# ============================================================ +def draw_hurwicz_interpolation(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 4)) + ax.set_title('Kryterium Hurwicza — wpływ α na wybór alternatywy', + fontsize=FS_TITLE + 1, fontweight='bold', pad=10) + + alphas = np.linspace(0, 1, 200) + + # V(Ai) = α * max_i + (1-α) * min_i + # A1: max=200, min=-100 + # A2: max=80, min=40 + # A3: max=30, min=30 + v1 = alphas * 200 + (1 - alphas) * (-100) + v2 = alphas * 80 + (1 - alphas) * 40 + v3 = alphas * 30 + (1 - alphas) * 30 + + ax.plot(alphas, v1, 'k-', lw=2, label='A₁ (fabryka): V = 300α − 100') + ax.plot(alphas, v2, 'k--', lw=2, label='A₂ (sklep): V = 40α + 40') + ax.plot(alphas, v3, 'k:', lw=2, label='A₃ (obligacje): V = 30') + + # Find crossover points + # A2 = A1: 40α + 40 = 300α - 100 → 140 = 260α → α = 140/260 ≈ 0.538 + alpha_cross_12 = 140 / 260 + v_cross_12 = 40 * alpha_cross_12 + 40 + + # A2 = A3: 40α + 40 = 30 → 40α = -10 → α = -0.25 (never — A2 always > A3) + # A1 = A3: 300α - 100 = 30 → 300α = 130 → α = 130/300 ≈ 0.433 + alpha_cross_13 = 130 / 300 + v_cross_13 = 30 + + ax.plot(alpha_cross_12, v_cross_12, 'ko', markersize=8, zorder=5) + ax.annotate(f'α ≈ {alpha_cross_12:.2f}\nA₁ = A₂', xy=(alpha_cross_12, v_cross_12), + xytext=(alpha_cross_12 + 0.12, v_cross_12 - 30), + fontsize=8, fontweight='bold', + arrowprops=dict(arrowstyle='->', color=LN, lw=1)) + + # Shade winning regions + ax.axvspan(0, alpha_cross_12, alpha=0.08, color='black', label='_') + ax.axvspan(alpha_cross_12, 1, alpha=0.15, color='black', label='_') + + ax.text(alpha_cross_12 / 2, -60, 'A₂ wygrywa\n(pesymistycznie)', + fontsize=8, ha='center', va='center', + bbox=dict(boxstyle='round', facecolor='white', edgecolor=LN)) + ax.text((alpha_cross_12 + 1) / 2, 160, 'A₁ wygrywa\n(optymistycznie)', + fontsize=8, ha='center', va='center', + bbox=dict(boxstyle='round', facecolor='white', edgecolor=LN)) + + # Special α values + ax.axvline(x=0, color=LN, lw=0.5, ls=':') + ax.axvline(x=1, color=LN, lw=0.5, ls=':') + ax.text(0, -115, 'α=0\nmaximin', fontsize=7, ha='center', va='top', fontweight='bold') + ax.text(1, -115, 'α=1\nmaximax', fontsize=7, ha='center', va='top', fontweight='bold') + + ax.set_xlabel('Współczynnik optymizmu α', fontsize=9) + ax.set_ylabel('V(Aᵢ) = α·max + (1−α)·min', fontsize=9) + ax.legend(fontsize=8, loc='upper left') + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + ax.set_xlim(-0.05, 1.05) + ax.axhline(y=0, color=LN, lw=0.3, ls='-') + ax.tick_params(labelsize=8) + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_hurwicz_alpha.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# 4. DECISION CRITERIA MNEMONIC MAP +# ============================================================ +def draw_criteria_mnemonic(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 6)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 8) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Mapa mnemoniczna — 6 kryteriów decyzyjnych', + fontsize=FS_TITLE + 2, fontweight='bold', pad=10) + + # Central node + draw_box(ax, 3.5, 3.5, 3, 1, 'MACIERZ\nWYPŁAT', fill=GRAY2, lw=2, + fontsize=11, fontweight='bold') + + # Criteria boxes around the center + criteria = [ + # (x, y, w, h, title, mnemonic, formula) + (0, 6.5, 3, 1.2, 'WARTOŚĆ OCZEKIWANA', '„Mam prawdopodobieństwa"', 'E[Aᵢ] = Σ pⱼ·aᵢⱼ'), + (3.5, 6.5, 3, 1.2, 'LAPLACE', '„Wszystko po równo"', 'V = Σaᵢⱼ / n'), + (7, 6.5, 3, 1.2, 'MAXIMAX', '„Optymista: max z max"', 'max maxⱼ aᵢⱼ'), + (0, 0.5, 3, 1.2, 'MAXIMIN (Wald)', '„Pesymista: max z min"', 'max minⱼ aᵢⱼ'), + (3.5, 0.5, 3, 1.2, 'HURWICZ', '„α pomiędzy"', 'α·max + (1−α)·min'), + (7, 0.5, 3, 1.2, 'SAVAGE', '„Min max żalu"', 'min maxⱼ rᵢⱼ'), + ] + + fills = [GRAY3, GRAY1, 'white', 'white', GRAY1, GRAY3] + + for i, (x, y, w, h, title, mnem, formula) in enumerate(criteria): + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.08", + lw=1.5, edgecolor=LN, facecolor=fills[i]) + ax.add_patch(rect) + ax.text(x + w/2, y + h*0.78, title, ha='center', va='center', + fontsize=8, fontweight='bold') + ax.text(x + w/2, y + h*0.45, mnem, ha='center', va='center', + fontsize=7, style='italic') + ax.text(x + w/2, y + h*0.15, formula, ha='center', va='center', + fontsize=7, fontweight='bold', family='monospace') + + # Arrows from center to each box + cx, cy = 5, 4 # center of macierz + bx, by = x + w/2, y + h/2 + if by > cy: + ax.annotate("", xy=(bx, y), xytext=(cx, 4.5), + arrowprops=dict(arrowstyle='->', color=LN, lw=1, + connectionstyle='arc3,rad=0')) + else: + ax.annotate("", xy=(bx, y + h), xytext=(cx, 3.5), + arrowprops=dict(arrowstyle='->', color=LN, lw=1, + connectionstyle='arc3,rad=0')) + + # Labels on arrows + ax.text(1.2, 5.6, 'znane p', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + ax.text(5, 5.6, 'p = 1/n', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + ax.text(8.7, 5.6, 'max ↑', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + ax.text(1.2, 2.5, 'min ↑', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + ax.text(5, 2.5, 'podaj α', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + ax.text(8.7, 2.5, 'macierz\nżalu', fontsize=7, ha='center', va='center', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5)) + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_criteria_mnemonic.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# 5. EXPECTED VALUE CRITERION WITH PROBABILITY BARS +# ============================================================ +def draw_expected_value(): + fig, axes = plt.subplots(1, 3, figsize=(8.27, 3.5), sharey=True) + fig.suptitle('Kryterium wartości oczekiwanej E[X] — rozkład wyników per alternatywa', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + # Probabilities: p1=0.5, p2=0.3, p3=0.2 + probs = [0.5, 0.3, 0.2] + states = ['S₁\n(dobra)\np=0.5', 'S₂\n(średnia)\np=0.3', 'S₃\n(zła)\np=0.2'] + alts = [ + ('A₁ (fabryka)', [200, 50, -100], 95), + ('A₂ (sklep)', [80, 70, 40], 69), + ('A₃ (obligacje)', [30, 30, 30], 30), + ] + + hatches = ['///', '...', 'xxx'] + + for idx, (ax, (name, vals, ev)) in enumerate(zip(axes, alts)): + # Bar: height = payoff, width proportional to probability + x_positions = [0, 0.6, 1.0] + widths = [p * 0.9 for p in probs] + + for i, (v, p, h) in enumerate(zip(vals, probs, hatches)): + color = 'white' if v >= 0 else GRAY2 + bar = ax.bar(x_positions[i], v, width=widths[i], color=color, + edgecolor=LN, hatch=h, lw=0.8, align='edge') + # Value label + offset = 8 if v >= 0 else -12 + ax.text(x_positions[i] + widths[i]/2, v + offset, f'{v}', + ha='center', va='center', fontsize=8, fontweight='bold') + # Probability contribution + contrib = v * p + ax.text(x_positions[i] + widths[i]/2, v/2, + f'{v}×{p}\n={contrib:.0f}', ha='center', va='center', + fontsize=6, style='italic') + + # Expected value line + ax.axhline(y=ev, color=LN, lw=2, ls='--') + ax.text(1.35, ev, f'E[X]={ev}', fontsize=8, fontweight='bold', + va='center', ha='left', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY1, edgecolor=LN)) + + ax.set_title(name, fontsize=9, fontweight='bold') + ax.set_xticks([0.225, 0.735, 1.09]) + ax.set_xticklabels(['S₁', 'S₂', 'S₃'], fontsize=7) + ax.axhline(y=0, color=LN, lw=0.5) + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + ax.tick_params(labelsize=7) + + # Star on winner + if ev == 95: + ax.text(0.7, ev + 20, '★ MAX', fontsize=9, fontweight='bold', + ha='center', va='bottom') + + axes[0].set_ylabel('Wypłata (tys. zł)', fontsize=8) + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_expected_value.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# 6. DECISION CONDITIONS SPECTRUM +# ============================================================ +def draw_conditions_spectrum(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 3.5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Warunki decyzyjne — spektrum wiedzy decydenta', + fontsize=FS_TITLE + 1, fontweight='bold', pad=10) + + # Three zones + zones = [ + (0.3, 1.5, 2.8, 2.5, 'PEWNOŚĆ', 'white', [ + 'Znamy dokładny wynik', + 'Przykład: lokata 5%', + 'Metoda: po prostu wybierz', + 'najlepszy wynik' + ]), + (3.5, 1.5, 2.8, 2.5, 'RYZYKO', GRAY1, [ + 'Znamy wyniki I prawdop.', + 'Przykład: gra w kości', + 'Metoda: wartość', + 'oczekiwana E[X]' + ]), + (6.7, 1.5, 2.8, 2.5, 'NIEPEWNOŚĆ', GRAY3, [ + 'Znamy wyniki, ale', + 'NIE znamy prawdop.', + 'Metody: Laplace, maximax,', + 'maximin, Hurwicz, Savage' + ]), + ] + + for x, y, w, h, title, fill, lines in zones: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1", + lw=2, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + w/2, y + h - 0.3, title, ha='center', va='center', + fontsize=11, fontweight='bold') + for i, line in enumerate(lines): + ax.text(x + w/2, y + h - 0.7 - i*0.4, line, ha='center', va='center', + fontsize=7) + + # Arrows between zones + ax.annotate("", xy=(3.4, 2.75), xytext=(3.15, 2.75), + arrowprops=dict(arrowstyle='->', color=LN, lw=2)) + ax.annotate("", xy=(6.6, 2.75), xytext=(6.35, 2.75), + arrowprops=dict(arrowstyle='->', color=LN, lw=2)) + + # Bottom: knowledge gradient bar + gradient_y = 0.5 + gradient_h = 0.5 + n_steps = 50 + for i in range(n_steps): + x = 0.3 + i * (9.2 / n_steps) + w = 9.2 / n_steps + 0.01 + gray_val = 1 - (i / n_steps) * 0.7 + rect = mpatches.Rectangle((x, gradient_y), w, gradient_h, lw=0, + facecolor=str(gray_val)) + ax.add_patch(rect) + + rect = mpatches.Rectangle((0.3, gradient_y), 9.2, gradient_h, lw=1.5, + edgecolor=LN, facecolor='none') + ax.add_patch(rect) + + ax.text(0.3, gradient_y - 0.15, 'Dużo wiedzy', fontsize=7, ha='left', va='top') + ax.text(9.5, gradient_y - 0.15, 'Mało wiedzy', fontsize=7, ha='right', va='top') + ax.text(4.95, gradient_y + gradient_h / 2, 'POZIOM WIEDZY DECYDENTA', + fontsize=8, fontweight='bold', ha='center', va='center', color='white') + + plt.tight_layout() + outpath = os.path.join(OUTPUT_DIR, 'q31_conditions_spectrum.png') + fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close(fig) + print(f" Saved: {outpath}") + + +# ============================================================ +# MAIN +# ============================================================ +if __name__ == '__main__': + print("Generating PYTANIE 31 diagrams...") + draw_criteria_comparison() + draw_regret_matrix() + draw_hurwicz_interpolation() + draw_criteria_mnemonic() + draw_expected_value() + draw_conditions_spectrum() + print("Done! All Q31 diagrams saved to:", OUTPUT_DIR) diff --git a/pytania/img/bellman_ford_negative_cycle.png b/pytania/img/bellman_ford_negative_cycle.png new file mode 100644 index 0000000..fc4035b Binary files /dev/null and b/pytania/img/bellman_ford_negative_cycle.png differ diff --git a/pytania/img/bellman_ford_negative_weights.png b/pytania/img/bellman_ford_negative_weights.png new file mode 100644 index 0000000..2bf072b Binary files /dev/null and b/pytania/img/bellman_ford_negative_weights.png differ diff --git a/pytania/img/q14_observer_card_filled.png b/pytania/img/q14_observer_card_filled.png new file mode 100644 index 0000000..36a2501 Binary files /dev/null and b/pytania/img/q14_observer_card_filled.png differ diff --git a/pytania/img/q14_pattern_language_navigation.png b/pytania/img/q14_pattern_language_navigation.png new file mode 100644 index 0000000..26bfaba Binary files /dev/null and b/pytania/img/q14_pattern_language_navigation.png differ diff --git a/pytania/img/q23_diy_thresholding.png b/pytania/img/q23_diy_thresholding.png new file mode 100644 index 0000000..154575d Binary files /dev/null and b/pytania/img/q23_diy_thresholding.png differ diff --git a/pytania/img/q23_diy_unet.png b/pytania/img/q23_diy_unet.png new file mode 100644 index 0000000..2a69790 Binary files /dev/null and b/pytania/img/q23_diy_unet.png differ diff --git a/pytania/img/q23_dot_product.png b/pytania/img/q23_dot_product.png new file mode 100644 index 0000000..b0eadba Binary files /dev/null and b/pytania/img/q23_dot_product.png differ diff --git a/pytania/img/q23_fc_vs_conv1x1.png b/pytania/img/q23_fc_vs_conv1x1.png new file mode 100644 index 0000000..6ae3326 Binary files /dev/null and b/pytania/img/q23_fc_vs_conv1x1.png differ diff --git a/pytania/img/q23_mean_shift.png b/pytania/img/q23_mean_shift.png new file mode 100644 index 0000000..eae1317 Binary files /dev/null and b/pytania/img/q23_mean_shift.png differ diff --git a/pytania/img/q23_mnemonics.png b/pytania/img/q23_mnemonics.png new file mode 100644 index 0000000..6e833b9 Binary files /dev/null and b/pytania/img/q23_mnemonics.png differ diff --git a/pytania/img/q23_normalized_cuts.png b/pytania/img/q23_normalized_cuts.png new file mode 100644 index 0000000..bf2a908 Binary files /dev/null and b/pytania/img/q23_normalized_cuts.png differ diff --git a/pytania/img/q23_otsu_bimodal.png b/pytania/img/q23_otsu_bimodal.png new file mode 100644 index 0000000..4420c3d Binary files /dev/null and b/pytania/img/q23_otsu_bimodal.png differ diff --git a/pytania/img/q23_receptive_field.png b/pytania/img/q23_receptive_field.png new file mode 100644 index 0000000..2b02bf3 Binary files /dev/null and b/pytania/img/q23_receptive_field.png differ diff --git a/pytania/img/q23_region_growing.png b/pytania/img/q23_region_growing.png new file mode 100644 index 0000000..91c363c Binary files /dev/null and b/pytania/img/q23_region_growing.png differ diff --git a/pytania/img/q23_relu.png b/pytania/img/q23_relu.png new file mode 100644 index 0000000..5a5dc27 Binary files /dev/null and b/pytania/img/q23_relu.png differ diff --git a/pytania/img/q23_transformer_attention.png b/pytania/img/q23_transformer_attention.png new file mode 100644 index 0000000..ee78d2f Binary files /dev/null and b/pytania/img/q23_transformer_attention.png differ diff --git a/pytania/img/q23_unet_arch.png b/pytania/img/q23_unet_arch.png new file mode 100644 index 0000000..4dcc888 Binary files /dev/null and b/pytania/img/q23_unet_arch.png differ diff --git a/pytania/img/q23_watershed.png b/pytania/img/q23_watershed.png new file mode 100644 index 0000000..4b58fed Binary files /dev/null and b/pytania/img/q23_watershed.png differ diff --git a/pytania/img/q24_anchor_boxes.png b/pytania/img/q24_anchor_boxes.png new file mode 100644 index 0000000..21458df Binary files /dev/null and b/pytania/img/q24_anchor_boxes.png differ diff --git a/pytania/img/q24_cnn_architecture.png b/pytania/img/q24_cnn_architecture.png new file mode 100644 index 0000000..5f35b38 Binary files /dev/null and b/pytania/img/q24_cnn_architecture.png differ diff --git a/pytania/img/q24_detection_tasks.png b/pytania/img/q24_detection_tasks.png new file mode 100644 index 0000000..34cdaa4 Binary files /dev/null and b/pytania/img/q24_detection_tasks.png differ diff --git a/pytania/img/q24_detector_from_classifier.png b/pytania/img/q24_detector_from_classifier.png new file mode 100644 index 0000000..efa805c Binary files /dev/null and b/pytania/img/q24_detector_from_classifier.png differ diff --git a/pytania/img/q24_detr_pipeline.png b/pytania/img/q24_detr_pipeline.png new file mode 100644 index 0000000..02de5ed Binary files /dev/null and b/pytania/img/q24_detr_pipeline.png differ diff --git a/pytania/img/q24_fpn.png b/pytania/img/q24_fpn.png new file mode 100644 index 0000000..c57fbd2 Binary files /dev/null and b/pytania/img/q24_fpn.png differ diff --git a/pytania/img/q24_haar_features.png b/pytania/img/q24_haar_features.png new file mode 100644 index 0000000..f4f67b4 Binary files /dev/null and b/pytania/img/q24_haar_features.png differ diff --git a/pytania/img/q24_hog_gradient_steps.png b/pytania/img/q24_hog_gradient_steps.png new file mode 100644 index 0000000..dda2b6a Binary files /dev/null and b/pytania/img/q24_hog_gradient_steps.png differ diff --git a/pytania/img/q24_hog_svm_pipeline.png b/pytania/img/q24_hog_svm_pipeline.png new file mode 100644 index 0000000..59f00f4 Binary files /dev/null and b/pytania/img/q24_hog_svm_pipeline.png differ diff --git a/pytania/img/q24_integral_image.png b/pytania/img/q24_integral_image.png new file mode 100644 index 0000000..437d408 Binary files /dev/null and b/pytania/img/q24_integral_image.png differ diff --git a/pytania/img/q24_iou_diagram.png b/pytania/img/q24_iou_diagram.png new file mode 100644 index 0000000..69bcb45 Binary files /dev/null and b/pytania/img/q24_iou_diagram.png differ diff --git a/pytania/img/q24_nms_steps.png b/pytania/img/q24_nms_steps.png new file mode 100644 index 0000000..7f6bd8c Binary files /dev/null and b/pytania/img/q24_nms_steps.png differ diff --git a/pytania/img/q24_rcnn_evolution.png b/pytania/img/q24_rcnn_evolution.png new file mode 100644 index 0000000..fcecb56 Binary files /dev/null and b/pytania/img/q24_rcnn_evolution.png differ diff --git a/pytania/img/q24_roi_pooling.png b/pytania/img/q24_roi_pooling.png new file mode 100644 index 0000000..8515ab6 Binary files /dev/null and b/pytania/img/q24_roi_pooling.png differ diff --git a/pytania/img/q24_sliding_window.png b/pytania/img/q24_sliding_window.png new file mode 100644 index 0000000..fef8642 Binary files /dev/null and b/pytania/img/q24_sliding_window.png differ diff --git a/pytania/img/q24_svm_hyperplane.png b/pytania/img/q24_svm_hyperplane.png new file mode 100644 index 0000000..aec786c Binary files /dev/null and b/pytania/img/q24_svm_hyperplane.png differ diff --git a/pytania/img/q24_two_vs_one_stage.png b/pytania/img/q24_two_vs_one_stage.png new file mode 100644 index 0000000..d5fda43 Binary files /dev/null and b/pytania/img/q24_two_vs_one_stage.png differ diff --git a/pytania/img/q24_viola_jones_cascade.png b/pytania/img/q24_viola_jones_cascade.png new file mode 100644 index 0000000..66882d0 Binary files /dev/null and b/pytania/img/q24_viola_jones_cascade.png differ diff --git a/pytania/img/q24_yolo_grid.png b/pytania/img/q24_yolo_grid.png new file mode 100644 index 0000000..a8480d4 Binary files /dev/null and b/pytania/img/q24_yolo_grid.png differ diff --git a/pytania/img/q31_conditions_spectrum.png b/pytania/img/q31_conditions_spectrum.png new file mode 100644 index 0000000..11ea625 Binary files /dev/null and b/pytania/img/q31_conditions_spectrum.png differ diff --git a/pytania/img/q31_criteria_comparison.png b/pytania/img/q31_criteria_comparison.png new file mode 100644 index 0000000..0e305db Binary files /dev/null and b/pytania/img/q31_criteria_comparison.png differ diff --git a/pytania/img/q31_criteria_mnemonic.png b/pytania/img/q31_criteria_mnemonic.png new file mode 100644 index 0000000..866c68a Binary files /dev/null and b/pytania/img/q31_criteria_mnemonic.png differ diff --git a/pytania/img/q31_expected_value.png b/pytania/img/q31_expected_value.png new file mode 100644 index 0000000..dd353b1 Binary files /dev/null and b/pytania/img/q31_expected_value.png differ diff --git a/pytania/img/q31_hurwicz_alpha.png b/pytania/img/q31_hurwicz_alpha.png new file mode 100644 index 0000000..ffd14f2 Binary files /dev/null and b/pytania/img/q31_hurwicz_alpha.png differ diff --git a/pytania/img/q31_regret_matrix.png b/pytania/img/q31_regret_matrix.png new file mode 100644 index 0000000..b78f4ba Binary files /dev/null and b/pytania/img/q31_regret_matrix.png differ diff --git a/pytania/questions/pytanie_02.md b/pytania/questions/pytanie_02.md index 37b454e..a662e7c 100644 --- a/pytania/questions/pytanie_02.md +++ b/pytania/questions/pytanie_02.md @@ -193,6 +193,10 @@ Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny): Po V−1 iteracjach dist nadal maleje → V-ta iteracja: dist[src] + weight < dist[dst] → return None +![Bellman-Ford — ujemne wagi vs Dijkstra](img/bellman_ford_negative_weights.png) + +![Bellman-Ford — wykrywanie cyklu ujemnego](img/bellman_ford_negative_cycle.png) + ![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png) **A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu): diff --git a/pytania/questions/pytanie_14_28.md b/pytania/questions/pytanie_14_28.md index 8d85dd0..0dd11d1 100644 --- a/pytania/questions/pytanie_14_28.md +++ b/pytania/questions/pytanie_14_28.md @@ -85,31 +85,75 @@ ### Katalogowanie — trzy filary metodologii -**1. Ustandaryzowany szablon opisu** — każdy wzorzec opisany wg tego samego formatu: -- **Nazwa** — jedno słowo/fraza: „Layered", „Observer" -- **Problem/Kontekst** — kiedy stosować -- **Siły (forces)** — konkurencyjne wymagania do pogodzenia -- **Rozwiązanie** — struktura, diagram, zachowanie -- **Konsekwencje** — tradeoffs: co zyskujemy, co tracimy -- **Powiązane wzorce** — jakie wzorce współgrają lub konkurują -- **Znane zastosowania** — real-world examples +Pytanie „JAK są katalogowane?" = jaką METODĘ stosujemy, żeby z setek wzorców zrobić przeszukiwalny, porównywalny, kompozytowalny system wiedzy. Odpowiedź: trzy filary, razem tworzące kompletną metodologię. -**2. Klasyfikacja wieloosiowa** — wzorce organizowane wzdłuż kilku osi jednocześnie: +![Trzy filary katalogowania wzorców](img/q14_three_pillars.png) + +**1. Ustandaryzowany szablon opisu (pattern template)** — każdy wzorzec opisany wg tego samego formatu, dzięki czemu można je porównywać „pole po polu". Mnemonik: **NaPSiRoKo**. + +| Pole | Skrót | Co zawiera | Przykład (Observer, GoF) | +|------|-------|------------|--------------------------| +| **Nazwa** | **Na** | jedno słowo/fraza | Observer | +| **Problem** | **P** | kiedy stosować? | Obiekt zmienia stan → wielu zależnych musi zareagować, ale nie chcemy ich hard-codować | +| **Siły** | **Si** | konkurencyjne wymagania | loose coupling vs koszt powiadomień (100 obserwatorów = 100 wywołań) | +| **Rozwiązanie** | **Ro** | struktura + zachowanie | Subject trzyma listę Observer; przy zmianie woła notify() na każdym | +| **Konsekwencje** | **Ko** | tradeoffs +/− | (+) luźne wiązanie, (−) kaskada powiadomień, memory leaks jeśli nie odrejestrujemy | +| Powiązane | — | wzorce pokrewne | Mediator (centralizuje), Pub/Sub (rozproszony wariant) | +| Znane zastosowania | — | real-world | Java Swing listeners, C# events, React useState → re-render | + +![Wypełniona karta wzorca Observer](img/q14_observer_card_filled.png) + +**2. Klasyfikacja wieloosiowa** — wzorce organizowane wzdłuż kilku osi jednocześnie, jak książki w bibliotece (dział + półka + autor). + +Osie klasyfikacji: - **Skala**: architektoniczny (cały system) → projektowy (klasa) → idiomatyczny (linia kodu) - **Domena problemu**: kreacyjne / strukturalne / behawioralne (GoF) albo warstwy / komunikacja / dekompozycja (POSA) - **Atrybut jakościowy**: wydajność, skalowalność, testowalność, dostępność -**3. Język wzorców (pattern language)** — wzorce referują się wzajemnie, tworząc graf: -- Microservices → wymaga → API Gateway, Service Discovery, Circuit Breaker -- Observer → wariant architektoniczny → Event-Driven Architecture -- Nawigacja: „mam problem X → wzorzec A → prowadzi do problemu Y → wzorzec B" +Konkretny przykład — jak GoF klasyfikuje 23 wzorce na dwóch osiach: -**Konkretne katalogi:** -- **POSA** (1996) — wzorce architektoniczne: Layers, Pipes & Filters, Broker, MVC, Microkernel -- **GoF** (1994) — 23 wzorce projektowe: kreacyjne (5), strukturalne (7), behawioralne (11) -- **EIP** (2003) — wzorce integracji: Message Channel, Router, Aggregator -- **PoEAA** (2002) — enterprise: Repository, Unit of Work, Domain Model, Active Record -- **Cloud Patterns** (~2015) — chmurowe: Circuit Breaker, Sidecar, Saga, Strangler Fig +| | Kreacyjne (5) | Strukturalne (7) | Behawioralne (11) | +|---|---|---|---| +| **Klasa** | Factory Method | Adapter (class) | Interpreter, Template Method | +| **Obiekt** | Abstract Factory, Builder, Prototype, Singleton | Adapter (obj), Bridge, Composite, Decorator, Facade, Flyweight, Proxy | Chain of Resp., Command, Iterator, Mediator, Memento, **Observer**, State, Strategy, Visitor | + +Observer jest w komórce: **behawioralny × obiekt**. Wiedzieć GDZIE wzorzec leży = szybsze przypomnienie i porównanie z sąsiadami (Mediator, State, Strategy — też behawioralne obiektowe). + +![Mapa katalogów wzorców](img/q14_catalog_map.png) + +**3. Język wzorców (pattern language)** — wzorce referują się wzajemnie, tworząc nawigacyjny graf „zobacz też". Sens: masz problem → stosujesz wzorzec A → A rodzi nowy problem → wzorzec B go rozwiązuje. + +Konkretna nawigacja w praktyce: + + Problem: „monolith nie skaluje się" + ↓ + Wzorzec: Microservices + ↓ wymaga + Problem: „jak routować żądania do serwisów?" + ↓ + Wzorzec: API Gateway + ↓ rodzi problem + Problem: „co gdy serwis nie odpowiada?" + ↓ + Wzorzec: Circuit Breaker + ↓ rodzi problem + Problem: „jak zachować spójność transakcji?" + ↓ + Wzorzec: Saga + +Każdy wzorzec w katalogu ma pole „Powiązane wzorce" — to linki w tym grafie. + +![Nawigacja w języku wzorców](img/q14_pattern_language_navigation.png) + +**Konkretne katalogi** (5 głównych — mnemonik **PGEP+C** = „Paweł Grał Efektownie Pod Chmurami"): + +| Katalog | Rok | Autorzy | Skala | Domena | Przykładowe wzorce | +|---------|-----|---------|-------|--------|--------------------| +| **POSA** | 1996 | Buschmann et al. | architektoniczny | systemy | Layers, Pipes & Filters, Broker, MVC, Microkernel | +| **GoF** | 1994 | Gamma, Helm, Johnson, Vlissides | projektowy | obiekty | Factory, Singleton, Observer, Strategy (23 łącznie) | +| **EIP** | 2003 | Hohpe & Woolf | integracyjny | komunikacja między-systemowa | Message Channel, Router, Aggregator | +| **PoEAA** | 2002 | Martin Fowler | projektowy/arch. | enterprise | Repository, Unit of Work, Domain Model, Active Record | +| **Cloud** | ~2015 | Microsoft/AWS | architektoniczny | chmura | Circuit Breaker, Sidecar, Saga, Strangler Fig | ### Przykładowe wzorce @@ -136,16 +180,43 @@ ### Jak zapamiętać -- **Mnemonik katalogów „PGEP+C"**: **P**OSA → **G**oF → **E**IP → **P**oEAA + **C**loud - - Historia: „**P**aweł **G**rał **E**fektownie **P**od **C**hmurami" - - Chronologicznie: GoF '94 → POSA '96 → PoEAA '02 → EIP '03 → Cloud ~'15 -- **Szablon wzorca „NaPSiRoKo"**: **Na**zwa, **P**roblem, **Si**ły, **Ro**związanie, **Ko**nsekwencje - - Wyobraź sobie kartonowe pudełko: etykieta (Nazwa) → co nie działa (Problem) → wagi na szalce (Siły) → instrukcja montażu (Rozwiązanie) → lista „+" i „−" na boku (Konsekwencje) -- **3 filary katalogowania**: Szablon + Klasyfikacja + Język wzorców - - Analogia do encyklopedii: każde hasło ma ten sam format (szablon), jest w kategorii z innymi hasłami tego typu (klasyfikacja), i ma „zobacz też" (język wzorców) -- **„Monolith first"** — rozdzielaj gdy znasz granice domen -- **Wzorzec = Nazwa + Problem + Rozwiązanie + Konsekwencje** (minimum do zapamiętania z dowolnego katalogu) +**Mnemonik 1 — szablon wzorca „NaPSiRoKo":** +- **Na**zwa → **P**roblem → **Si**ły → **Ro**związanie → **Ko**nsekwencje +- Historyjka: „**Na**pisałem **P**roblem na kartce, **Si**ły mnie ciągnęły w dwie strony, **Ro**związałem go, a **Ko**nsekwencje spisałem na odwrocie" +- Wyobraź sobie kartonowe pudełko: etykieta (Nazwa) → co nie działa (Problem) → wagi na szalce (Siły) → instrukcja montażu (Rozwiązanie) → lista „+" i „−" na boku (Konsekwencje) + +**Mnemonik 2 — katalogi „PGEP+C" = „Paweł Grał Efektownie Pod Chmurami":** + + P = POSA (1996, systemy) „Paweł" + G = GoF (1994, obiekty) „Grał" + E = EIP (2003, integracja) „Efektownie" + P = PoEAA (2002, enterprise) „Pod" + C = Cloud (~2015, chmura) „Chmurami" + +- Chronologicznie: GoF '94 → POSA '96 → PoEAA '02 → EIP '03 → Cloud ~'15 +- Skala rośnie: GoF (obiekty) → PoEAA (aplikacja) → POSA/EIP (system) → Cloud (infrastruktura) + +**Mnemonik 3 — trzy filary katalogowania „SzKlaJ" = „Szklany Jar":** +- **Sz**ablon opisu (NaPSiRoKo) — każde hasło w tym samym formacie +- **Kla**syfikacja wieloosiowa — hasła posortowane w kategorie (jak dział w bibliotece) +- **J**ęzyk wzorców — hasła mają „zobacz też" (graf nawigacyjny) +- Analogia: encyklopedia. Każde hasło ma ten sam format (**Sz**ablon), jest w kategorii z innymi hasłami tego typu (**Kla**syfikacja), i ma „zobacz też" (**J**ęzyk wzorców) + +**Mnemonik 4 — GoF 3 kategorie „KSB" = „Kto Stworzył Budynek?":** +- **K**reacyjne (5) — JAK tworzyć obiekty? (Factory, Singleton, Builder, Prototype, Abstract Factory) +- **S**trukturalne (7) — JAK składać obiekty? (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy) +- **B**ehawioralne (11) — JAK obiekty komunikują? (Observer, Strategy, Command, State, Iterator...) +- Zapamiętaj liczby: 5 + 7 + 11 = 23 + +**Szybka ściąga — wzorzec na obronie:** +- Wzorzec = Nazwa + Problem + Rozwiązanie + Konsekwencje (minimum do zapamiętania z dowolnego katalogu) +- „Monolith first" — rozdzielaj gdy znasz granice domen - Katalogi wg skali: POSA = systemy, GoF = obiekty, EIP = komunikacja międzysystemowa -→ Diagramy do druku: `pytania/img/q14_pattern_template.png`, `pytania/img/q14_catalog_map.png` +→ Diagramy do druku: +- `pytania/img/q14_pattern_template.png` — szablon NaPSiRoKo +- `pytania/img/q14_catalog_map.png` — mapa katalogów PGEP+C +- `pytania/img/q14_three_pillars.png` — trzy filary katalogowania +- `pytania/img/q14_observer_card_filled.png` — wypełniona karta wzorca Observer +- `pytania/img/q14_pattern_language_navigation.png` — nawigacja w języku wzorców diff --git a/pytania/questions/pytanie_16.md b/pytania/questions/pytanie_16.md index 2ff9e29..26b1c2a 100644 --- a/pytania/questions/pytanie_16.md +++ b/pytania/questions/pytanie_16.md @@ -12,6 +12,261 @@ --- +**Robot przemysłowy (industrial robot)** — manipulator: ramię mechaniczne z 4–7 osiami obrotu (degrees of freedom), zamocowane na stałe, sterowane komputerowo. Typowe zastosowania: spawanie, malowanie, paletyzacja, montaż. Standard ISO 8373: „automatycznie sterowany, reprogramowalny, wielozadaniowy manipulator". Przykłady: ABB IRB 6700 (spawanie karoserii), KUKA KR 210 (paletyzacja), FANUC M-20iA (montaż elektroniki). W odróżnieniu od cobotów (collaborative robots), roboty przemysłowe pracują w klatkach bezpieczeństwa — nie wolno wchodzić w ich zasięg podczas pracy. + + Robot przemysłowy — budowa: + [Podstawa] → [Oś 1: obrót] → [Oś 2: ramię] → [Oś 3: łokieć] + → [Oś 4: nadgarstek] → [Oś 5: pochylenie] → [Oś 6: obrót TCP] + ↓ + [Narzędzie/chwytak] + +--- + +**Język ogólnego przeznaczenia (general-purpose language)** — język programowania zaprojektowany do rozwiązywania DOWOLNYCH problemów: aplikacje webowe, bazy danych, gry, systemy operacyjne, AI. Przykłady: C++, Python, Java, C#, Rust. Nie ma wbudowanych komend ruchu robota — trzeba pisać biblioteki lub sterowniki od zera. W kontekście robotyki: C++ i Python używane z frameworkami (ROS, MoveIt), ale NIE są to języki specjalizowane. + + // C++ (ogólny) — żeby ruszyć robotem, musisz: + robot.setJointAngle(0, 45.0); // sam zarządzasz komunikacją + robot.setJointAngle(1, 30.0); // sam pilnujesz kinematyki + robot.sendCommand(); // sam wysyłasz pakiety + + // RAPID (specjalizowany) — komenda ruchu to prymityw języka: + MoveL pTarget, v500, fine, tool1; // gotowe, 1 linia + +**Składnia (syntax)** — reguły, JAK pisać poprawne polecenia w danym języku. Składnia określa: jakie słowa kluczowe istnieją, jak je łączyć, jak kończyć instrukcje, jak grupować bloki kodu. Analogia: składnia to gramatyka języka naturalnego — „Ala ma kota" jest poprawne, „kota Ala ma" jest zrozumiałe, ale „ma Ala kota" w programowaniu dałoby błąd. + + Różne składnie — ta sama instrukcja „jeśli x > 5, zrób coś": + C-like: if (x > 5) { doSomething(); } + Pascal-like: IF x > 5 THEN doSomething; ENDIF + Python-like: if x > 5:\n do_something() + +**Typy danych (data types)** — kategorie wartości, jakie język rozpoznaje. Typ mówi kompilatorowi/interpreterowi ile pamięci zająć i jakie operacje są dozwolone. Języki robotów mają UNIKALNE typy, których nie znajdziesz w C++ czy Pythonie. + + Typy ogólne (istnieją w każdym języku): + INT / num → liczba całkowita: 42, -7, 0 + REAL / float → liczba zmiennoprzecinkowa: 3.14, -0.001 + BOOL → prawda/fałsz: TRUE, FALSE + STRING → tekst: "Hello" + + Typy SPECYFICZNE dla robotyki (nie istnieją w C++/Python): + robtarget → pozycja + orientacja + konfiguracja ramienia (RAPID) + E6POS → x,y,z + a,b,c (kąty Eulera) + osie zewnętrzne (KRL) + POSITION → pozycja kartezjańska (Karel) + pose → [x, y, z, rx, ry, rz] (URScript) + tooldata → definicja narzędzia (TCP + masa + środek ciężkości) + speeddata → prędkość TCP + prędkość obrotowa + zonedata → strefa zbliżenia (jak blisko celu jechać) + +**Instrukcja (instruction/statement)** — pojedyncze polecenie w programie. Komputer/robot wykonuje instrukcje po kolei (sekwencyjnie). W językach robotów instrukcje dzielą się na: + + 1. Ruchu: MoveL pTarget, v500, fine, tool1; + 2. I/O: SetDO doGripper, 1; + 3. Czekania: WaitTime 0.5; + 4. Kontroli: IF sensor = TRUE THEN ... + 5. Przypisania: nCycles := nCycles + 1; + 6. Wywołania: PickPart; (wywołanie procedury) + +--- + +**Zadanie robotyczne (robotic task)** — czynność, którą robot ma wykonać w ramach procesu produkcyjnego. Składa się z sekwencji ruchów, operacji I/O i logiki. Przykłady: +- Pick & place: podnieś obiekt z punktu A, przenieś do punktu B +- Spawanie: jedź po ścieżce spoiny z włączonym łukiem +- Paletyzacja: układaj pudła na palecie w warstwy 4×3 +- Montaż: włóż kołek w otwór z kontrolą siły + +**Ruch robota (robot motion)** — zmiana pozycji ramienia robota w czasie. Każdy ruch jest zdefiniowany przez cztery elementy: CEL (dokąd), TYP (jak — liniowo, stawowo, po łuku), PRĘDKOŚĆ (jak szybko), PRECYZJA (strefa zbliżenia). W językach robotów ruch to PRYMITYW — jedno polecenie, nie pętla obliczeń. + + Trzy typy ruchu — jak robot jedzie z A do B: + + PTP / MoveJ (joint): + A ●─ ─ ─ ── ● B ← ścieżka TCP nieprzewidywalna (krzywa) + Ale najszybszy! Interpolacja w przestrzeni stawów. + + LIN / MoveL (linear): + A ●──────────● B ← TCP jedzie po prostej linii + Wolniejszy, ale precyzyjna ścieżka. Wymaga ciągłego IK. + + CIRC / MoveC (circular): + A ●╲ ╱● B ← TCP jedzie po łuku kołowym + ╲ ● H ╱ H = punkt pomocniczy (definiuje łuk) + ╲─────╱ + +**Definiowanie ruchów** — w językach specjalizowanych ruch definiujesz JEDNĄ instrukcją z parametrami. Nie musisz pisać pętli sterowania silnikami — język ukrywa kinematykę odwrotną, planowanie trajektorii i sterowanie serwomechanizmami. + + MoveL pTarget, v500, z10, tGripper; + │ │ │ │ └── narzędzie (jaki TCP) + │ │ │ └── precyzja (strefa zbliżenia) + │ │ └── prędkość (500 mm/s) + │ └── cel (pozycja docelowa) + └── typ ruchu (liniowy) + +--- + +**I/O cyfrowe i analogowe (digital/analog I/O)** — interfejsy wejścia/wyjścia robota do komunikacji z urządzeniami zewnętrznymi (chwytaki, czujniki, przenośniki, lampy sygnalizacyjne). + +- **I/O cyfrowe (digital)** — sygnał ma dwa stany: 0 (OFF) lub 1 (ON). Jak wyłącznik światła. Użycie: włącz/wyłącz chwytak, sprawdź czy czujnik wykrył obiekt, sygnalizuj gotowość do PLC. + +- **I/O analogowe (analog)** — sygnał ciągły w zakresie, np. 0–10V lub 4–20mA. Jak regulator głośności. Użycie: ustaw siłę chwytaka proporcjonalnie (nie tylko ON/OFF), odczytaj temperaturę z termopary, ustaw prędkość przenośnika. + + I/O cyfrowe — jak włącznik (ON/OFF): + SetDO doGripper, 1; // RAPID: włącz chwytak (ON) + SetDO doGripper, 0; // RAPID: wyłącz chwytak (OFF) + OUT 1 TRUE // KRL: włącz wyjście 1 + DOUT[1] = ON // Karel: włącz wyjście 1 + set_digital_out(0, True) // URScript + + I/O analogowe — jak potencjometr (wartość ciągła): + SetAO aoForce, 5.7; // RAPID: ustaw wyjście analogowe na 5.7V + // np. siła chwytaka proporcjonalna do napięcia: + // 0V = brak siły, 10V = maksymalna siła + +--- + +**Pozycja (position)** — punkt w przestrzeni, do którego robot ma dojechać. Opisywana trzema współrzędnymi: x, y, z (odległości w milimetrach od początku układu współrzędnych). Sama pozycja to za mało — robot musi też wiedzieć, JAK narzędzie ma być obrócone w tym punkcie (orientacja). + + Pozycja: (x=400, y=200, z=100) + Znaczenie: 400mm do przodu, 200mm w prawo, 100mm w górę + od początku układu współrzędnych robota (podstawa) + +**Układ kartezjański (Cartesian coordinate system)** — układ współrzędnych xyz, w którym każdy punkt w przestrzeni jest opisany trzema prostopadłymi odległościami od początku. Nazwa od René Descartesa (Kartezjusz). W robotyce: pozycja TCP wyrażona w mm: x=400, y=200, z=100. Alternatywa: przestrzeń stawowa (kąty: q₁=30°, q₂=45°, q₃=-10°…), która opisuje tę samą pozycję z perspektywy silników. + + Układ kartezjański: + Z ↑ + │ ● TCP (400, 200, 100) + │ ╱ + │ ╱ + │ ╱ + ├──────→ Y + ╱ + ╱ + X + + Przestrzeń stawowa (ta sama pozycja, ale w kątach): + q₁ = 26.6°, q₂ = 45.0°, q₃ = -12.3°, q₄ = 0°, q₅ = 57.3°, q₆ = 0° + +**Orientacja (orientation)** — kierunek, w jakim narzędzie (TCP) jest skierowane w danym punkcie. Pozycja mówi GDZIE jest, orientacja mówi JAK jest obrócone. Wyrażana jako kwaternion (q1,q2,q3,q4) w RAPID lub kąty Eulera (A,B,C) w KRL. + + Ta sama pozycja, różne orientacje: + Orientacja 1: chwytak skierowany w dół ↓ (spawanie poziomej płyty) + Orientacja 2: chwytak skierowany w bok → (wkładanie elementu w otwór) + Orientacja 3: chwytak skierowany w górę ↑ (podpieranie od spodu) + + W RAPID (kwaternion): [1, 0, 0, 0] = narzędzie pionowo w dół + W KRL (kąty Eulera): A=0, B=0, C=180 = obrót 180° wokół Z + +**Konfiguracja ramienia (robot configuration)** — dla jednej pozycji + orientacji robot 6-osiowy może mieć do 8 różnych ustawień stawów (jak zgięcie łokcia do góry lub do dołu). Konfiguracja to dodatkowy parametr, który mówi robotowi KTÓRE rozwiązanie kinematyki odwrotnej wybrać. + + Ta sama pozycja, dwie konfiguracje: + + Konfiguracja 1 ("łokieć do góry"): Konfiguracja 2 ("łokieć na dół"): + ╱──╲ ╲──╱ + ╱ ╲ ╲ ╲ + ───╱ ● TCP ───╱ ● TCP + + W RAPID: robtarget = [[x,y,z], [orientacja], [konfiguracja], [osie_zewn]] + Konfiguracja = [cf1, cf4, cf6, cfx] — opisuje kwadranty stawów + +--- + +**Język strukturalny (structured language)** — język z jasno zdefiniowanymi blokami kodu: procedury (PROC/ENDPROC), pętle (WHILE/ENDWHILE, FOR/ENDFOR), warunki (IF/ELSE/ENDIF). Przeciwieństwo: kod spaghetti z GOTO i nienazwanymi skokami. RAPID jest strukturalny — program składa się z modułów (MODULE/ENDMODULE) zawierających procedury. Łatwy do czytania i utrzymania. + + Strukturalność RAPID: + MODULE MainModule ← moduł (pojemnik) + PROC PickPart() ← procedura (nazwany blok) + IF ready THEN ← warunek + MoveL ...; + ENDIF + ENDPROC ← wyraźny koniec bloku + ENDMODULE + +**Język wielozadaniowy (multitasking language)** — język umożliwiający uruchamianie wielu programów (tasków) jednocześnie na jednym kontrolerze. W RAPID: jeden task steruje ruchem ramienia, drugi monitoruje czujniki bezpieczeństwa, trzeci komunikuje się z PLC. Każdy task działa „równolegle" (w rzeczywistości: szybkie przełączanie kontekstu). + + RAPID — wielozadaniowość: + Task 1 (MAIN): MoveL → MoveL → MoveL... (sterowanie ruchem) + Task 2 (SAFETY): WHILE TRUE DO (ciągły monitoring) + IF DI(emergency) THEN Stop; + ENDWHILE + Task 3 (COMM): czytaj/wysyłaj dane do PLC (komunikacja) + ← kontroler przełącza się między taskami co ~4ms + +--- + +**Język Pascal-like** — język, którego składnia przypomina język Pascal (Niklaus Wirth, 1970). Cechy: bloki zaczynają się słowem kluczowym i kończą `END` (nie nawiasami `{}`), zmienne deklarowane na początku bloku (`VAR`/`DECL`), średniki jako separatory, brak rozróżniania wielkości liter (case-insensitive). KRL i Karel mają składnię Pascal-like. + + Pascal: KRL (KUKA): + PROGRAM example; DEF PickAndPlace() + VAR x: INTEGER; DECL INT x + BEGIN ; (brak BEGIN/END w KRL, + x := 10; ; ale DEF/END pełni tę rolę) + IF x > 5 THEN IF x > 5 THEN + WriteLn('tak'); ; zrób coś + END; ENDIF + END. END + +**Approximacja (approximation)** — termin KUKA odpowiadający „strefie zbliżenia" w RAPID. Parametr `$APO.CDIS = 10` oznacza: robot zaczyna skręcać w stronę następnego celu, gdy jest 10mm od bieżącego. Efekt identyczny jak `z10` w RAPID — płynniejszy ruch, szybszy cykl, ale TCP nie dochodzi dokładnie do zaprogramowanego punktu. + + RAPID: MoveL p1, v500, z10, tool1; // strefa = 10mm + KRL: $APO.CDIS = 10 // approximacja = 10mm + LIN XTarget C_DIS // C_DIS = zastosuj approximację + +--- + +**Język Python-like** — składnia inspirowana Pythonem: brak nawiasów klamrowych `{}`, proste wywołania funkcji, brak deklaracji typów, czytelny kod przypominający pseudokod. URScript jest Python-like — ale UWAGA: wcięcia w URScript NIE definiują bloków (w odróżnieniu od Pythona). Bloki kończą się słowem `end`. + + Python: URScript: + def pick(): def pick(): + target = [0.4, 0.2] target = p[0.4, 0.2, 0.1, 3.14, 0, 0] + move(target) movel(target, a=0.5, v=0.3) + end ← URScript wymaga 'end' (Python nie) + +**Język skryptowy (scripting language)** — język interpretowany (nie kompilowany): program czytany i wykonywany linia po linii w trakcie działania, bez osobnego kroku kompilacji. Zalety: szybkie testowanie (zmień kod → uruchom od razu), brak czekania na kompilację. Wady: wolniejszy od skompilowanego kodu (ale dla robota to nie problem — wąskim gardłem jest fizyczny ruch, nie szybkość interpretera). URScript jest skryptowy. + + Kompilowany (C++, Karel): Skryptowy (URScript, Python): + 1. Napisz kod 1. Napisz kod + 2. Skompiluj (czekaj...) 2. Uruchom natychmiast + 3. Przenieś na kontroler ← brak kroku kompilacji + 4. Uruchom + +**Typowanie dynamiczne (dynamic typing)** — zmiennej NIE deklarujesz typu — język sam go rozpoznaje w momencie przypisania. Zmienna może zmieniać typ w trakcie programu. Przeciwieństwo: typowanie statyczne (C++, KRL) — typ deklarujesz z góry i nie może się zmienić. + + Dynamiczne (URScript/Python): Statyczne (KRL): + x = 42 ← x jest liczbą DECL INT x ← x musi być INT + x = "hello" ← teraz x tekst x = 42 ← OK + ← BRAK błędu x = "hello" ← BŁĄD kompilacji! + +**Niski próg wejścia (low barrier to entry)** — URScript jest łatwy do nauki, bo łączy cechy ułatwiające start: +1. Brak deklaracji typów — nie musisz znać `INT`, `REAL`, `E6POS` +2. Składnia Python-like — jeśli znasz Pythona, czytasz URScript od razu +3. Prosty model: `movel(cel, prędkość)` — intuicyjne wywołanie +4. Coboty adresowane do małych firm bez zespołu programistów +5. Darmowy symulator URSim — uczysz się bez kupowania robota +6. Polyscope (GUI) — operator może programować drag & drop + + Krzywa uczenia się (orientacyjnie): + URScript: dni (Python-like, prosty) + RAPID: tygodnie (strukturalny, wiele typów danych) + KRL: tygodnie (Pascal-like, dwa pliki) + Karel/TP: dni (TP) / tygodnie (Karel pełny) + ROS+C++: miesiące (framework + język ogólny + Linux) + +--- + +**Język proceduralny (procedural language)** — paradygmat programowania, w którym program to sekwencja PROCEDUR (funkcji) wywoływanych po kolei. Każda procedura wykonuje konkretne zadanie. Brak obiektów i klas (to byłby język obiektowy). Wszystkie języki robotów są proceduralne — program to lista kroków: jedź tu, zamknij chwytak, jedź tam, otwórz chwytak. + + Proceduralny = lista kroków: Obiektowy = obiekty i metody: + PickPart(); robot.pick(objectA); + MoveTo(placePos); objectA.moveTo(placePos); + OpenGripper(); gripper.open(); + +**Język C-like** — składnia inspirowana językiem C: nawiasy klamrowe `{}` lub `:=` do przypisań, średniki na końcu instrukcji, zmienne ze znakiem `$` dla zmiennych systemowych. PDL2 (Comau) jest C-like: `$DOUT[1] := TRUE` przypomina składnię C z operatorem przypisania. + + C: PDL2 (Comau): + int x = 10; VAR x : INTEGER + if (x > 5) { IF x > 5 THEN + output[1] = 1; $DOUT[1] := TRUE + } ENDIF + +--- + **Poziomy abstrakcji T-R-M-S:** **Task-level (poziom zadania)** — najwyższy: opisujesz CO robot ma zrobić, nie JAK. „Podnieś A, połóż na B." Robot sam planuje ruchy. Przykłady: PDDL, Behavior Trees. @@ -74,6 +329,41 @@ --- +### Specjalizowane języki programowania robotów — przegląd + +Specjalizowane języki programowania robotów to języki stworzone wyłącznie do sterowania robotami przemysłowymi. Nie są językami ogólnego przeznaczenia (jak C++ czy Python) — ich składnia, typy danych i instrukcje są zaprojektowane wokół zadań robotycznych: definiowania ruchów, obsługi I/O cyfrowych i analogowych, zarządzania narzędziami (TCP) i reagowania na błędy w czasie rzeczywistym. + +**Główne specjalizowane języki producentów (robot-level):** + +1. **RAPID (ABB)** — strukturalny, wielozadaniowy. Komendy ruchu: `MoveL`, `MoveJ`, `MoveC`. Pozycje opisywane typem `robtarget` (kartezjańska + orientacja + konfiguracja). Dwupoziomowy parametr ruchu: prędkość (`v500` = 500 mm/s) + strefa zbliżenia (`z10`, `fine`). Obsługuje wielowątkowość (wiele tasków). Symulator: RobotStudio. + +2. **KRL — KUKA Robot Language** — Pascal-like (`DEF/END`, `DECL`). Program = dwa pliki: `.src` (kod) + `.dat` (pozycje). Komendy ruchu: `PTP`, `LIN`, `CIRC`. Prędkość ustawiana zmienną systemową `$VEL.CP`. Approximacja (`C_DIS`) zamiast stref. Symulator: KUKA.Sim Pro. + +3. **Karel (FANUC)** — Pascal-like (`PROGRAM/BEGIN/END`). Dwa tryby: Karel (pełny tekstowy) i TP (Teach Pendant — uproszczony, listowy, numerowane linie). W praktyce fabrycznej dominuje TP — operatorzy bez wykształcenia programistycznego uczą się numerowanych linii `L P[2] 500mm/sec FINE`. Symulator: ROBOGUIDE. + +4. **URScript (Universal Robots)** — Python-like, skryptowy, dynamicznie typowany. Zaprojektowany dla cobotów (robotów współpracujących). Unikalne: `force_mode()` (sterowanie siłą), `freedrive_mode()` (ręczne prowadzenie). Niski próg wejścia. Symulator: URSim (darmowy). + +5. **PDL2 (Comau)** — proceduralny, C-like. `MOVE LINEAR TO`, `$DOUT[1] := TRUE`. Stosowany głównie w automotive (Fiat/Stellantis). Symulator: RoboSim. + +**Języki task-level (planowanie):** + +6. **PDDL (Planning Domain Definition Language)** — deklaratywny język opisu problemów planowania. Definiujesz stany, akcje (warunki + efekty) i cel — planner automatycznie znajduje sekwencję akcji. Nie steruje robotem bezpośrednio, lecz generuje plan, który robot-level realizuje. + +7. **Behavior Trees** — struktury drzew zachowań (Sequence, Selector, Action, Condition). Stosowane w robotyce i grach. Alternatywa dla maszyn stanów — łatwiejsze w rozbudowie i debugowaniu. + +**Middleware i frameworki (uniwersalne, nie jednego producenta):** + +8. **ROS / ROS 2** — middleware publish/subscribe, programowanie w Python/C++. NIE jest językiem specjalizowanym per se, ale jest de facto standardem łączącym roboty wielu producentów. Biblioteka **MoveIt** (motion planning) przełamuje vendor lock-in. + +9. **Orocos** — framework C++ do hard real-time sterowania (<1 ms). Wypełnia lukę ROS w pętlach regulacji wymagających gwarancji czasowych. + +**Wspólne cechy języków specjalizowanych:** +- Wbudowane typy pozycji (kartezjańska, stawowa) — nie istnieją w C++ czy Pythonie +- Komendy ruchu jako prymitywy języka (`MoveL`, `LIN`, `movel`) — nie wywołania bibliotek +- Obsługa I/O cyfrowego/analogowego jako element składni +- Parametry ruchu (prędkość, strefa zbliżenia, narzędzie) jako argumenty instrukcji +- Brak wskaźników, zarządzania pamięcią, struktur danych ogólnego przeznaczenia — język zoptymalizowany pod jedno zastosowanie + ### Klasyfikacja wg poziomu abstrakcji: **T-R-M-S** 1. **Task-level** — „Podnieś A, połóż na B" (PDDL, Behavior Trees) diff --git a/pytania/questions/pytanie_23.md b/pytania/questions/pytanie_23.md index 3cd3b74..66f50aa 100644 --- a/pytania/questions/pytanie_23.md +++ b/pytania/questions/pytanie_23.md @@ -59,6 +59,42 @@ --- +#### Pojęcia kluczowe dla progowania i Otsu + +**Wariancja (variance, σ²)** — miara tego, jak bardzo wartości RÓŻNIĄ SIĘ od swojej średniej. Im większa wariancja, tym bardziej „rozrzucone" są dane. Wzór: σ² = Σ(xᵢ - μ)² / n, gdzie μ to średnia. + + Przykład 1 — MAŁA wariancja (dane skupione): + wartości: [48, 50, 52, 49, 51] średnia μ = 50 + σ² = ((48-50)² + (50-50)² + (52-50)² + (49-50)² + (51-50)²) / 5 + = (4 + 0 + 4 + 1 + 1) / 5 = 2.0 + + Przykład 2 — DUŻA wariancja (dane rozrzucone): + wartości: [10, 90, 30, 80, 50] średnia μ = 52 + σ² = ((10-52)² + (90-52)² + (30-52)² + (80-52)² + (50-52)²) / 5 + = (1764 + 1444 + 484 + 784 + 4) / 5 = 896.0 + + Mała σ² = punkty blisko średniej = dane JEDNORODNE + Duża σ² = punkty daleko od średniej = dane RÓŻNORODNE + +**Wewnątrzklasowa (within-class)** — „wewnątrz klasy" oznacza, że mierzymy wariancję OSOBNO dla każdej grupy (klasy), a potem ważymy wynik proporcją pikseli w grupie. Jeśli klasa 0 ma piksele [30, 50, 45] a klasa 1 ma piksele [180, 200, 190], to σ²_wewnątrz = (udział_kl0 × σ²_kl0) + (udział_kl1 × σ²_kl1). + +**Wariancja wewnątrzklasowa (within-class variance)** — obliczasz wariancję KAŻDEJ klasy osobno, ważysz przez udział pikseli w tej klasie, sumujesz. Jeśli σ²_wewnątrz jest MAŁA → klasy są „jednorodne" (piksele w klasie 0 mają podobne jasności, piksele w klasie 1 też). + +**Co to znaczy „klasy jednorodne"?** — jednorodna klasa to taka, w której WSZYSTKIE piksele mają podobne wartości. Np. klasa „tło" ma jasności [195, 200, 198, 205] → jednorodna (σ² mała). Klasa mieszająca tło i obiekt [30, 200, 50, 190] → niejednorodna (σ² duża). Otsu szuka progu T, który daje NAJBARDZIEJ jednorodne klasy. + +**Histogram bimodalny (bimodal histogram)** — histogram z DWOMA wyraźnymi „garbami" (pikami). „Bi" = dwa, „modal" = moda (najczęstsza wartość). Typowy dla obrazów z jednym obiektem na tle — garb 1 odpowiada ciemnym pikselom (obiekt), garb 2 jasnym (tło). Otsu działa TYLKO gdy histogram jest bimodalny — bo szuka progu MIĘDZY garbami. + + Garb 1 (ciemne~60): piksele obiektu + Garb 2 (jasne~190): piksele tła + Dolina między garbami → tu Otsu stawia próg T! + + Gdyby histogram miał JEDEN garb (unimodalny) → brak naturalnego + podziału → Otsu wybierze losowy próg → słaby wynik. + +![Histogram bimodalny, wariancja wewnątrzklasowa i jednorodność klas — Otsu](img/q23_otsu_bimodal.png) + +--- + **Thresholding (progowanie)** — najprostsza metoda segmentacji. Pomysł: każdy piksel ma wartość jasności (0=czarny, 255=biały). Wybierz PRÓG T: piksel > T → klasa 1 (obiekt), piksel ≤ T → klasa 0 (tło). Działa lepiej niż się wydaje na prostych obrazach (tekst na kartce, RTG, dokumenty). Obraz (jasność pikseli): [50][200][180][30][220][190] @@ -71,16 +107,55 @@ Problem: JAK wybrać T? Ręcznie → subiektywne. Rozwiązanie → Otsu. -**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). + Mnemonik: „PRÓG na bramce" — jak bramkarz, przepuszcza piksele jaśniejsze od T, + blokuje ciemniejsze. + +**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). Patrz diagram powyżej. + + Pseudokod Otsu: + best_T = 0 + min_var = ∞ + for T in 0..255: + c0 = piksele z jasność ≤ T + c1 = piksele z jasność > T + w0 = len(c0) / len(all_pixels) + w1 = len(c1) / len(all_pixels) + var = w0 * variance(c0) + w1 * variance(c1) + if var < min_var: + min_var = var + best_T = T + return best_T + + Mnemonik: „AUTO-bramkarz Otsu" — sam sprawdza 256 progów i wybiera najlepszy. + +--- + +#### Pojęcia kluczowe dla Region Growing **Region Growing (rozrastanie regionu)** — zaczynasz od jednego piksela „ziarna" (seed) wybranego ręcznie lub automatycznie. Sprawdzasz sąsiadów: jeśli sąsiad jest PODOBNY (np. |jasność_sąsiada - jasność_regionu| < próg), dodaj go do regionu. Powtarzaj aż nie ma więcej podobnych sąsiadów. Następnie nowy seed → nowy region. - Seed piksel (100,100) ma jasność 150 - Sąsiad (101,100) ma jasność 153 → |153-150|=3 < próg 10 → DODAJ - Sąsiad (100,101) ma jasność 200 → |200-150|=50 > próg 10 → ODRZUĆ (granica!) - Region rośnie jak „plama" od seeda +**Dlaczego seed „ręcznie LUB automatycznie"?** — to dwa różne scenariusze użycia: - Pseudokod: + RĘCZNY seed: + - Użytkownik klika myszką na obraz: „tu jest obiekt" + - Użycie: segmentacja interaktywna (Photoshop „magic wand", + narzędzia medyczne do zaznaczania guzów na RTG) + - Zaleta: precyzyjny, użytkownik wie co chce segmentować + - Wada: wymaga człowieka → nie skaluje się do 10 000 obrazów + + AUTOMATYCZNY seed — metody: + 1. Siatka (grid): seed co N pikseli (np. co 50 px na obrazie 500×500 → 100 seedów) + 2. Lokalne ekstrema histogramu: znajdź najczęstszą jasność → seed tam + 3. Losowanie: wylosuj K punktów jako seedy + 4. Analiza gradientu: piksele w „płaskich" regionach (brak krawędzi) → dobre seedy + + Dlaczego OR a nie AND? + Bo to ALTERNATYWNE podejścia — albo człowiek wybiera (mało i precyzyjnie), + albo algorytm wybiera (dużo i szybko, ale mniej precyzyjnie). + +![Region Growing: seed ręczny vs automatyczny, krok po kroku, fale BFS](img/q23_region_growing.png) + + Pseudokod Region Growing: region = {seed} queue = [seed] while queue not empty: @@ -90,106 +165,189 @@ region.add(neighbor) queue.append(neighbor) - Problem: over-segmentation — drobne szumy → małe regiony - -**Watershed (metoda zlewiska)** — traktuje obraz jak mapę topograficzną: wartość jasności piksela = wysokość terenu. Ciemne piksele = doliny, jasne = szczyty. Algorytm „zalewa" mapę wodą od najniższych punktów (minimów). Gdy woda z dwóch dolin się spotyka — tam jest GRANICA segmentu (grań). - - Obraz jako mapa wysokości: - ████████ - ██ ██ ← jasne piksele = szczyty (granice) - █ dolina █ - █ (obiekt) █ - █ █ - ██ dolina ██ ← kolejna dolina (inny segment) - ████████ - - Algorytm: zalewamy od dołu → woda spotyka się na graniach → SEGMENTY - - Problem: MASYWNA over-segmentation — każde lokalne minimum (nawet szum) → osobna dolina - Rozwiązanie: marker-controlled watershed — ręcznie podaj „ziarna" (markers) - zamiast zalewać od KAŻDEGO minimum - -**Mean Shift** — iteracyjne przesuwanie okna (jądra) do punktu o najwyższej gęstości pikseli w przestrzeni cech. Cechy to np. (jasność, x, y) lub (R, G, B, x, y). Piksele, które zbiegają do tego samego maksimum gęstości, tworzą jeden segment. Wolny: O(n²), ale nie wymaga podania liczby segmentów. - - Wyobraź sobie rozsypane kulki na stole (= piksele w przestrzeni cech) - Każda kulka „toczy się" w kierunku najbliższej „góry kulek" (max gęstości) - Kulki, które dotoczyły się do tej samej góry → jeden segment - -**Normalized Cuts** — modeluje obraz jako graf: piksele = węzły, krawędzie łączą sąsiednie piksele z wagą = PODOBIEŃSTWO (im bardziej podobne, tym wyższa waga). Szukamy CIĘCIA grafu (podział na grupy) minimalizującego stosunek ciętych krawędzi do rozmiaru grup. „Znormalizowane" → unika tworzenia malutkich segmentów. O(n³) — bardzo kosztowny: obraz 100×100 = 10 000 węzłów → 10¹² operacji! + Mnemonik: „PLAMA atramentu" — seed to kropla atramentu na papierze, + rozlewa się na podobne (jasne) miejsca, zatrzymuje się na granicach. --- -**Sieć neuronowa (neural network)** — model uczenia maszynowego inspirowany biologicznymi neuronami. Składa się z warstw „neuronów" — każdy neuron oblicza ważoną sumę wejść + bias, przepuszcza przez funkcję aktywacji (np. ReLU = max(0,x)), i przekazuje wynik dalej. Sieć uczy się automatycznie z danych: dostaje pary (obraz, poprawna mapa segmentacji), dostosowuje wagi by minimalizować błąd. +#### Pojęcia kluczowe dla Watershed - Neuron: output = ReLU(w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + bias) - ReLU(x) = max(0, x) — prosta, ale bardzo skuteczna funkcja aktywacji - Uczenie: porównaj predykcję sieci z poprawną mapą (label) → oblicz błąd (loss) - → backpropagation → aktualizuj wagi → powtórz miliony razy +**Watershed (metoda zlewiska)** — traktuje obraz jak mapę topograficzną: wartość jasności piksela = wysokość terenu. Ciemne piksele = doliny, jasne = szczyty. Algorytm „zalewa" mapę wodą od najniższych punktów (minimów). Gdy woda z dwóch dolin się spotyka — tam jest GRANICA segmentu (grań). -**CNN (Convolutional Neural Network)** — sieć, której kluczowym elementem jest warstwa konwolucyjna (splotowa). Zamiast łączyć KAŻDY piksel z KAŻDYM neuronem (co byłoby niewykonalne — obraz 640×480 = 307 200 neuronów wejściowych!), CNN przesuwany mały filtr (np. 3×3 pikseli) po obrazie, obliczając w każdym miejscu iloczyn skalarny filtra z fragmentem obrazu. +![Watershed: obraz jako mapa topograficzna, zalewanie, over-segmentation i marker-controlled watershed](img/q23_watershed.png) - Co robi konwolucja? Filtr 3×3 „jedzie" po obrazie jak wycieraczka: + Algorytm: + 1. Zamień obraz na „mapę wysokości" (jasność = wysokość) + 2. Znajdź wszystkie lokalne minima (najciemniejsze punkty) + 3. „Zalewaj" od minimów — woda rośnie równomiernie + 4. Gdy woda z dwóch dolin się spotyka → postaw TAMĘ (granicę segmentu) + 5. Kontynuuj aż cały obraz zalany - Filtr (edge detector): Fragment obrazu: Wynik konwolucji: - [-1 0 1] [50 50 200] (-1·50 + 0·50 + 1·200 + - [-1 0 1] * [50 50 200] = -1·50 + 0·50 + 1·200 + - [-1 0 1] [50 50 200] -1·50 + 0·50 + 1·200) = 450 + Problem: MASYWNA over-segmentation — każde lokalne minimum (nawet szum!) → osobna dolina + Rozwiązanie: marker-controlled watershed — użytkownik podaje markery (seedy), + zalewamy TYLKO od tych markerów - Duża wartość → tu jest KRAWĘDŹ (przejście ciemne→jasne) + Mnemonik: „ZALEWANIE terenu" — wyobraź sobie model terenu z plasteliny w wannie. + Powoli nalewasz wodę → doliny się wypełniają → granie gór = granice segmentów. - Hierarchia cech w CNN (wyuczona automatycznie!): - Warstwa 1: krawędzie (|, —, /, \) - Warstwa 2: tekstury (paski, siatki, plamy) - Warstwa 3: części (koła, oczy, krawędź dachu) - Warstwa 4+: obiekty (twarz, samochód, drzewo) +--- -**Encoder-Decoder** — architektura segmentacji: encoder ZMNIEJSZA rozdzielczość obrazu (downsampling — pooling), wydobywając coraz bardziej abstrakcyjne cechy (krawędzie → tekstury → obiekty). Decoder ZWIĘKSZA rozdzielczość (upsampling — dekonwolucja lub interpolacja), odtwarzając mapę segmentacji o pełnej rozdzielczości. +#### Pojęcia kluczowe dla Mean Shift - Encoder (zmniejsza): [224×224] →pool→ [112×112] →pool→ [56×56] →pool→ [28×28] →pool→ [14×14] - Decoder (zwiększa): [14×14] →up→ [28×28] →up→ [56×56] →up→ [112×112] →up→ [224×224] +**Okno (window) / jądro (kernel)** — w kontekście Mean Shift to koło (lub kula w wielowymiarowej przestrzeni) o ustalonej szerokości (bandwidth = promień h) wokół aktualnego punktu. Wewnątrz okna algorytm oblicza „średnią ważoną" pozycji pikseli. Okno = jądro — to synonim. Nazwa „jądro" pochodzi od estymacji jądrowej gęstości (kernel density estimation, KDE). - Dlaczego nie sklasyfikować od razu KAŻDEGO piksela osobno? - Bo pojedynczy piksel nie ma kontekstu — nie wiesz, czy piksel o wartości 150 - to fragment nieba czy samochodu. Encoder-decoder widzi KONTEKST (cały obiekt) - i jednocześnie tworzy wynik o PEŁNEJ ROZDZIELCZOŚCI. + Okno o promieniu h = 30 wokół punktu (100, 150): + Bierze WSZYSTKIE piksele, których cechy (jasność, x, y) + są w odległości ≤ 30 od (100, 150). + Oblicza ich średnią → przesuwa okno NA TĘ ŚREDNIĄ. + Powtarza aż okno się „zatrzyma" (przesunięcie < ε). -**Skip connections (połączenia skrótowe)** — połączenia „na skróty" łączące warstwy encodera z odpowiadającymi warstwami decodera. Problem: encoder traci detale przestrzenne (GDZIE dokładnie jest krawędź) podczas poolingu. Skip connections PRZENOSZĄ te detale z encodera wprost do decodera, umożliwiając precyzyjne granice segmentów. +**Najwyższa gęstość (density peak)** — punkt w przestrzeni cech, gdzie jest NAJWIĘKSZE skupisko pikseli. Jak najwyższy szczyt góry w 3D. Mean Shift = „przesuń w kierunku średniej" → iteracyjnie zbliża się do szczytu gęstości. - Bez skip connections: decoder „wie" ŻE tu jest samochód, ale granice są rozmyte - Ze skip connections: decoder „wie" ŻE tu jest samochód AND DOKŁADNIE GDZIE jest krawędź +**Przestrzeń cech (feature space)** — każdy piksel jest opisany nie tylko pozycją (x, y) ale też cechami koloru (jasność, R, G, B). Przestrzeń cech to przestrzeń wielowymiarowa, np. (R, G, B, x, y) = 5 wymiarów. Piksele o podobnych kolorach i blisko siebie będą blisko w przestrzeni cech → tworzą klastry (skupiska). -**FCN (Fully Convolutional Network, 2015)** — pierwsza sieć w pełni konwolucyjna do segmentacji. Kluczowa innowacja: **zastąpienie warstw fully-connected** (FC → stały rozmiar wejścia) **konwolucjami** (→ dowolny rozmiar wejścia). Klasyczny CNN (np. VGG, AlexNet) kończy się warstwami FC, które wymagają stałego rozmiaru (np. 224×224). FCN zamienia FC na Conv 1×1, co pozwala przetwarzać obraz o DOWOLNYM rozmiarze i zwracać mapę segmentacji. + Piksel A: (x=100, y=200, R=30, G=25, B=35) → punkt w 5D + Piksel B: (x=102, y=201, R=32, G=27, B=33) → BLISKO A w 5D + Piksel C: (x=105, y=198, R=200, G=210, B=220) → DALEKO od A w 5D (inny kolor!) + → A i B w jednym segmencie, C w innym - Klasyczny CNN: Conv → Conv → Pool → ... → FC(4096) → FC(1000) → "kot" - FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa [H×W×C] - ↑ skip connections z encodera +**Dlaczego Mean Shift NIE wymaga podania liczby segmentów?** — W K-means musisz podać K=3 (trzy klastry) ZANIM uruchomisz algorytm. Mean Shift działa inaczej: każdy piksel startuje i „toczy się" do najbliższego szczytu gęstości. Ile jest szczytów = tyle segmentów. Algorytm sam ODKRYWA liczbę klastrów. Parametrem jest tylko bandwidth (szerokość okna h): duże h → mało szczytów → mało segmentów; małe h → dużo szczytów → dużo segmentów. -**U-Net (2015)** — encoder-decoder w kształcie litery „U" ze skip connections realizowanymi przez **concatenation** (złączenie) — cechy z encodera są DOKLEJANE do cech decodera w odpowiedniej warstwie. Zaprojektowany dla segmentacji medycznej, gdzie zbiory danych są MAŁE (np. 30 zdjęć RTG), więc U-Net intensywnie używa data augmentation (obroty, odbicia, elastyczne deformacje). +![Mean Shift: przestrzeń cech, jądro przesuwane do max gęstości, dlaczego bez K](img/q23_mean_shift.png) - Encoder ──skip (concat)──→ Decoder - ↓ ──skip (concat)──→ ↑ - ↓ ──skip (concat)──→ ↑ - bottleneck (najgłębsza warstwa) + Pseudokod Mean Shift: + for each pixel p: + x = p.features # np. (R, G, B, pos_x, pos_y) + repeat: + window = all pixels within distance h from x + x_new = weighted_mean(window) + if |x_new - x| < epsilon: + break + x = x_new + p.cluster = x # zbieżny punkt = ID klastra - Dlaczego „U"? Bo wizualnie encoder schodzi w dół (↓), bottleneck na dole, - decoder wraca do góry (↑) — tworząc kształt litery U. - Dlaczego concat a nie dodawanie? Więcej informacji — encoder features + decoder features - → sieć sama decyduje, które informacje wykorzystać. + Mnemonik: „KULKI toczą się do dołków" — rozsyp kulki na nierównym stole, + każda toczy się do najbliższego zagłębienia. Ile dołków = tyle segmentów. -**DeepLab v3+** — Google. Kluczowe innowacje: +--- -**Atrous (dilated) convolutions** — konwolucje z „dziurami" (fr. à trous = z dziurami). Standardowy filtr 3×3 patrzy na 3×3 = 9 sąsiednich pikseli. Atrous convolution z rate=2 patrzy na piksele z odstępem 2 — efektywnie widzi 5×5 obszar, ALE używa TYCH SAMYCH 9 parametrów (wag). Większe receptive field (pole widzenia) za darmo! +#### Pojęcia kluczowe dla Normalized Cuts - Zwykła konwolucja 3×3: [x][x][x] receptive field = 3×3 - Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5×5, 9 parametrów! - Dilated (rate=3): [x][ ][ ][x][ ][ ][x] receptive field = 7×7, 9 parametrów! +**Cięcie grafu (graph cut)** — graf to zbiór węzłów (pikseli) połączonych krawędziami (z wagami = podobieństwo). „Ciąć graf" to znaleźć LINIĘ dzielącą węzły na grupy, tak aby krawędzie „przecięte" tą linią miały niską wagę (= łączyły niepodobne piksele), a krawędzie wewnątrz grup miały wysoką wagę (= łączyły podobne piksele). - Dlaczego to ważne? Segmentacja wymaga KONTEKSTU — żeby wiedzieć, że piksel to - „droga", musisz zobaczyć otaczające budynki i niebo. Większe receptive field = więcej kontekstu. +**Jak szukamy cięcia?** — Naiwnie: sprawdź WSZYSTKIE możliwe podziały → wykładnicza złożoność. Normalized Cuts zamienia problem na rozwiązanie „problemu wartości własnych" (eigenvalue problem) macierzy Laplacianu grafu. Drugi najmniejszy wektor własny wskazuje, które piksele należą do grupy A (wartości dodatnie) a które do B (wartości ujemne). -**ASPP (Atrous Spatial Pyramid Pooling)** — równoległe zastosowanie atrous convolutions z WIELOMA rate (np. 6, 12, 18) + global average pooling, potem połączenie wyników. Każdy rate widzi kontekst w INNEJ skali → multi-scale features. +**Dlaczego „znormalizowane" (normalized)?** — Zwykłe cięcie (min-cut) ma wadę: preferuje odcinanie MALUTKICH grup (1 piksel odcięty = małe cięcie). Normalizowanie dzieli koszt cięcia przez rozmiar grup → duże, zrównoważone segmenty. -**Transformer-based (SegFormer, Mask2Former)** — najnowsze podejście zastępujące CNN transformerami. Kluczowy mechanizm: **self-attention** — każdy piksel „pyta" WSZYSTKIE inne piksele: „jak bardzo jesteś ze mną powiązany?" CNN widzi tylko lokalne okno (3×3, 5×5), a self-attention widzi CAŁY obraz naraz → lepsze rozumienie globalnych zależności (np. „ten piksel jest częścią tego samego samochodu co piksel 500 pikseli dalej"). Cena: O(n²) pamięci (n = liczba pikseli), ale jakość SOTA na benchmarkach. +![Normalized Cuts: obraz jako graf, cięcie, algorytm krok po kroku](img/q23_normalized_cuts.png) + + Pseudokod Normalized Cuts (uproszczony): + # 1. Zbuduj macierz podobieństwa W + for each pair of pixels (i, j): + W[i,j] = exp(-|color_i - color_j|^2 / sigma^2) # jeśli sąsiedzi + W[i,j] = 0 # jeśli odlegli + + # 2. Macierz stopni D + D = diag(sum(W, axis=1)) # D[i,i] = suma wiersza i + + # 3. Rozwiąż problem wartości własnych + (D - W) * y = lambda * D * y + # Weź DRUGI najm. wektor własny y (pierwszy = trywialny) + + # 4. Podziel piksele + segment_A = {i : y[i] > 0} + segment_B = {i : y[i] <= 0} + + Mnemonik: „CIĘCIE sznurków" — piksele połączone sznurkami (mocne = podobne). + Tnij SŁABE sznurki → dwie grupy. Normalizacja = nie odcinaj samotnych pikseli. + +--- + +#### Pojęcia kluczowe dla sieci neuronowych + +**ReLU (Rectified Linear Unit)** — najpopularniejsza funkcja aktywacji w sieciach neuronowych. Wzór: ReLU(x) = max(0, x). Jeśli wejście jest ujemne → wynik = 0 (neuron „milczy"). Jeśli wejście jest dodatnie → wynik = x (neuron „przepuszcza" sygnał bez zmiany). Prosta, ale bardzo skuteczna — szybsza od starszych funkcji (sigmoid, tanh), bo nie wymaga obliczania exp(). + + ReLU(-3) = max(0, -3) = 0 ← neuron „wyłączony" + ReLU(0) = max(0, 0) = 0 ← na granicy + ReLU(2.5) = max(0, 2.5) = 2.5 ← neuron „włączony", przekazuje 2.5 + + Dlaczego nie po prostu f(x) = x (bez progu)? + Bo liniowość → cała sieć = jedna warstwa liniowa (tracisz głębokość). + ReLU jest NIELINIOWA (ma „zakręt" w 0) → pozwala sieci uczyć się + skomplikowanych wzorców. + +![ReLU: wykres funkcji, dlaczego ReLU, przykład numeryczny](img/q23_relu.png) + +**Iloczyn skalarny (dot product)** — operacja na dwóch wektorach (listach liczb) dająca JEDNĄ liczbę. Mnożysz odpowiednie elementy parami i sumujesz wyniki. W CNN konwolucja = iloczyn skalarny filtra × fragment obrazu. Duży wynik = wektory „podobne" (filtr pasuje do fragmentu). + + a = [1, 3, -2] b = [4, -1, 5] + a · b = 1·4 + 3·(-1) + (-2)·5 = 4 - 3 - 10 = -9 + + W konwolucji: + filtr = [-1, 0, 1, -1, 0, 1, -1, 0, 1] (spłaszczony 3×3) + fragment = [50, 50, 200, 50, 50, 200, 50, 50, 200] + dot = (-1)·50 + 0·50 + 1·200 + ... = 450 → duży = krawędź! + +![Iloczyn skalarny: definicja, geometryczna interpretacja, użycie w konwolucji](img/q23_dot_product.png) + +--- + +**Warstwa Fully Connected (FC, gęsta, dense)** — warstwa, w której KAŻDY neuron jest połączony z KAŻDYM wejściem. Obraz 7×7×512 (po konwolucjach) = 25 088 wartości. FC z 4096 neuronami = 25 088 × 4 096 = **~103 miliony wag**. Wady: (1) wymaga STAŁEGO rozmiaru wejścia (zawsze 7×7×512), (2) traci informację GDZIE coś jest (spłaszcza przestrzeń na wektor 1D). + +**Konwolucja (convolution)** — operacja przesuwania małego filtra (np. 3×3) po obrazie. W każdej pozycji oblicza iloczyn skalarny filtra × fragment obrazu → jedną liczbę. TE SAME wagi filtra użyte w KAŻDEJ pozycji → dzielenie parametrów. Zachowuje informację przestrzenną (GDZIE coś jest). + +**Conv 1×1 (konwolucja punktowa)** — filtr o rozmiarze 1×1 pikseli. „Patrzy" na JEDEN piksel, ale WSZYSTKIE kanały (np. 512). Działa jak FC, ale OSOBNO dla KAŻDEGO piksela → zachowuje mapę H×W. FCN zamienia FC na Conv 1×1: zamiast spłaszczyć 7×7×512 → 25 088 → FC, robi Conv1×1 na KAŻDYM z 7×7 pikseli × 512 kanałów → mapa 7×7×C (C = liczba klas). + +**Jak FCN zamienia FC na Conv 1×1?** — Klasyczny CNN: ostatnia mapa cech 7×7×512 → FLATTEN → wektor 25 088 → FC → 1000 klas → „to jest kot". FCN: ostatnia mapa cech H×W×512 → Conv1×1(512→C) → mapa H×W×C → upsample do pełnej rozdzielczości. Kluczowa różnica: NIE spłaszczamy → możemy przetwarzać obraz o DOWOLNYM rozmiarze. + +**Skip connections z encodera** — w encoder-decoder encoder zmniejsza obraz (pooling): 224→112→56→28→14. W tym procesie traci DETALE przestrzenne (dokładne krawędzie). Skip connections = „drogi na skróty" — cechy z wczesnych warstw encodera (pełne detali) są przekazywane WPROST do odpowiednich warstw decodera. Decoder wie CO i GDZIE. + +![FCN: warstwa FC vs Conv 1×1, konwolucja, skip connections](img/q23_fc_vs_conv1x1.png) + +--- + +**U-Net — dlaczego kształt „U"?** — Narysuj architekturę: encoder zmniejsza rozdzielczość (bloki idą w DÓŁ po lewej stronie), bottleneck jest na dole, decoder zwiększa rozdzielczość (bloki idą W GÓRĘ po prawej stronie). Wizualnie tworzy literę „U". „Encoder schodzi w dół" = każda warstwa encodera ma MNIEJSZĄ rozdzielczość (224→112→56→28), wizualizowane jako bloki o malejącym rozmiarze ułożone jeden pod drugim. + +**Concatenation (konkatenacja, złączenie)** — operacja „sklejania" dwóch tensorów wzdłuż osi kanałów. Jeśli encoder na poziomie 2 daje mapę 128×128×64 kanałów, a decoder na poziomie 2 daje mapę 128×128×64 kanałów, to concatenation = 128×128×**128** kanałów (64+64). Różni się od DODAWANIA (addition), które daje 128×128×64 (element-wise sum). Concatenation zachowuje WIĘCEJ informacji — sieć sama wybiera, które kanały wykorzystać. + + Dodawanie (ResNet-style): + encoder [a, b, c] + decoder [x, y, z] = [a+x, b+y, c+z] → 3 kanały + + Concatenation (U-Net-style): + encoder [a, b, c] ++ decoder [x, y, z] = [a, b, c, x, y, z] → 6 kanałów! + → więcej informacji, sieć sama zdecyduje co ważne + +![U-Net: architektura w kształcie U, skip connections z concatenation, encoder ↓ decoder ↑](img/q23_unet_arch.png) + + Mnemonik U-Net: „Litera U — w dół i w górę" — encoder schodzi ↓ (zmniejsza), + decoder wraca ↑ (zwiększa), między nimi mosty (skip = concat). + +--- + +**Receptive field (pole widzenia, pole recepcyjne)** — ile pikseli WEJŚCIOWYCH wpływa na JEDEN piksel wyjściowy. Konwolucja 3×3 → RF = 3×3. Dwie konwolucje 3×3 pod rząd → RF = 5×5 (druga widzi 3×3 fragmenty, z których każdy widział 3×3 → efektywnie 5×5). Większe RF = neuron widzi większy kontekst = lepiej rozumie co to za piksel. + +**Dlaczego większe RF jest lepsze?** — Pojedynczy piksel o jasności 150 może być fragmentem nieba LUB samochodu. Patrząc na otoczenie 3×3 → nadal nie wiesz. Patrząc na otoczenie 50×50 → widzisz budynki obok → „to droga!". Segmentacja wymaga KONTEKSTU globalnego. + +**Rate (współczynnik dylatacji)** — parametr atrous (dilated) convolution. Rate=1 = zwykła konwolucja (filtr dotyka sąsiadów). Rate=2 = filtr próbkuje co DRUGI piksel → RF rośnie z 3×3 do 5×5 przy TYCH SAMYCH 9 wagach. Rate=3 → RF = 7×7. Większy kontekst za darmo (bez dodatkowych parametrów). + +**Global Average Pooling (GAP)** — operacja redukcji: mapa cech H×W×C → 1×1×C. Dla KAŻDEGO kanału oblicza ŚREDNIĄ ze wszystkich H×W pikseli. Wynik: jeden wektor o wymiarze C, reprezentujący „średnią informację" z całego obrazu. RF = nieskończone (cały obraz). Używane w ASPP DeepLab jako jedna z równoległych gałęzi. + + Mapa cech 7×7×512: + Kanał 0: macierz 7×7 wartości → średnia → jedna liczba + Kanał 1: macierz 7×7 wartości → średnia → jedna liczba + ... + Kanał 511: macierz 7×7 wartości → średnia → jedna liczba + Wynik: wektor [avg₀, avg₁, ..., avg₅₁₁] → 1×1×512 + +![Receptive field: zwykła vs dilated konwolucja, rate, global average pooling](img/q23_receptive_field.png) + +--- + +**Transformer** — architektura sieci neuronowej zaproponowana w 2017 (Vaswani et al., „Attention Is All You Need"). Oryginalnie dla NLP (tłumaczenie), od 2020 (ViT — Vision Transformer) stosowana w wizji komputerowej. Kluczowy mechanizm: **self-attention** — każdy element (piksel/token) „pyta" WSZYSTKIE inne elementy: „jak bardzo jesteś ze mną powiązany?". Każdy element tworzy trzy wektory: Q (Query — czego szukam?), K (Key — co oferuję), V (Value — moja wartość). Attention = softmax(Q·Kᵀ / √d) · V. Koszt: O(n²) pamięci (n = liczba elementów). + +**SOTA (State Of The Art)** — najlepszy znany wynik na danym benchmarku (zbiorze testowym) w danym momencie. Np. „Mask2Former osiąga mIoU 57.8% na ADE20K — to aktualny SOTA". SOTA ciągle się zmienia — każdy nowy paper może pobić poprzedni rekord. + +![Transformer: CNN lokalny vs Transformer globalny, self-attention Q/K/V, SOTA](img/q23_transformer_attention.png) --- @@ -202,8 +360,6 @@ **Focal Loss** — modyfikacja cross-entropy redukująca wpływ łatwych przykładów, skupiająca uczenie na trudnych. Kluczowa przy class imbalance (np. 99% tła, 1% obiekt). -**Receptive field** — ile wejścia „widzi" jeden neuron. Większe receptive field = kontekst globalny. Atrous convolutions zwiększają receptive field bez zwiększania parametrów. - --- ### Problem: czym jest segmentacja obrazu? @@ -242,28 +398,36 @@ Segmentacja obrazu to **przypisanie etykiety klasy KAŻDEMU pikselowi** obrazu. Metody niewymagające uczenia maszynowego — oparte na ręcznie zdefiniowanych regułach (próg, podobieństwo, struktura grafu). -| Metoda | Idea | Wada | Złożoność | -|--------|------|------|-----------| -| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) | -| **Otsu** | automatyczny próg (min wariancja wewnątrzklasowa) | j.w. ale dobiera T sam | O(n·L) | -| **Region Growing** | dodawaj sąsiednie piksele o podobnej wartości | over-segmentation, zależy od seeda | O(n) | -| **Watershed** | obraz = mapa wysokości, granice = granie gór | over-segmentation | O(n log n) | -| **Mean Shift** | iteracyjnie przesuwaj jądro do max gęstości | wolny | O(n²) | -| **Normalized Cuts** | piksele = węzły grafu, minimalizuj znormalizowane cięcie | bardzo wolny | O(n³) | +| Metoda | Idea | Wada | Złożoność | Mnemonik | +|--------|------|------|-----------|----------| +| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) | „PRÓG na bramce" | +| **Otsu** | automatyczny próg (min wariancja wewnątrzklasowa) | j.w. ale dobiera T sam | O(n·L) | „AUTO-bramkarz" | +| **Region Growing** | dodawaj sąsiednie piksele o podobnej wartości | over-segmentation, zależy od seeda | O(n) | „PLAMA atramentu" | +| **Watershed** | obraz = mapa wysokości, granice = granie gór | over-segmentation | O(n log n) | „ZALEWANIE terenu" | +| **Mean Shift** | iteracyjnie przesuwaj jądro do max gęstości | wolny | O(n²) | „KULKI toczą się" | +| **Normalized Cuts** | piksele = węzły grafu, minimalizuj znormalizowane cięcie | bardzo wolny | O(n³) | „CIĘCIE sznurków" | -**Przykład — Thresholding (Otsu):** +#### DIY Przykład — Thresholding (Otsu) krok po kroku - Obraz grayscale: [30][200][180][45][210][190] - Otsu automatycznie dobiera próg T=128: - Wynik: [ 0 ][ 1 ][ 1 ][ 0][ 1 ][ 1 ] - Zastosowanie: oddzielenie tekstu od tła (OCR), analiza zdjęć RTG +Poniższy diagram pokazuje CAŁY pipeline progowania Otsu od obrazu wejściowego do wyniku. Obraz syntetyczny 64×64 z ciemnym kołem na jasnym tle — typowy przypadek bimodalny. -**Przykład — Watershed:** +![DIY Thresholding + Otsu: obraz → histogram bimodalny → progowanie → szukanie min σ² → pseudokod → wynik](img/q23_diy_thresholding.png) - Obraz traktowany jako mapa topograficzna: - Jasne piksele = szczyty, ciemne = doliny - "Zalewamy" od minimów → woda spotyka się na graniach → GRANICE segmentów - Problem: za wiele minimów → over-segmentation → potrzeba markers + Pseudokod Otsu (Python-style): + best_T, min_var = 0, float('inf') + for T in range(256): + c0 = pixels[pixels <= T] # piksele ciemne + c1 = pixels[pixels > T] # piksele jasne + if len(c0) == 0 or len(c1) == 0: + continue + w0 = len(c0) / len(pixels) # udział klasy 0 + w1 = len(c1) / len(pixels) # udział klasy 1 + var = w0 * variance(c0) + w1 * variance(c1) # σ² wewnątrzklasowa + if var < min_var: + min_var = var + best_T = T + # best_T = optymalny próg (np. 128) + result = (pixels > best_T).astype(int) # binaryzacja **Wspólna wada klasycznych metod:** wymagają ręcznego doboru parametrów (próg, seed, kernel), nie uczą się cech z danych, słabe na złożonych obrazach naturalnych. @@ -279,23 +443,26 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy Decoder: cechy [14] → [28] → [56] → [112] → [224×224] (odtwarza MAPĘ) bottleneck -| Sieć | Rok | Kluczowa innowacja | Use case | -|------|-----|-------------------|----------| -| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end | -| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | -| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | -| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | -| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | +| Sieć | Rok | Kluczowa innowacja | Use case | Mnemonik | +|------|-----|-------------------|----------|----------| +| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end | „FC → Conv 1×1" | +| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | „Litera U + mosty" | +| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | „DZIURY w filtrze" | +| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | „WSZYSCY ze WSZYSTKIMI" | +| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | „WSZYSCY ze WSZYSTKIMI" | **FCN (Fully Convolutional Network):** + Mnemonik: „FC → Conv 1×1 = otwieramy bramkę dla DOWOLNEGO rozmiaru" Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot" - FCN: Conv → Conv → Pool → ... → Conv → Upsample → mapa pikseli - Innowacja: zamiana FC na Conv → wejście dowolnego rozmiaru + FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa pikseli + Innowacja: zamiana FC na Conv1×1 → wejście dowolnego rozmiaru Skip connections: łączą cechy z encodera → zachowują detale przestrzenne **U-Net:** + Mnemonik: „Litera U + mosty" — schodzisz w dół, wracasz w górę, + po drodze mosty (skip connections z concat) przenoszą detale. Encoder (↓) Decoder (↑) [64]────skip────→[64] ← skip connections = concatenation [128]───skip───→[128] (przenosi detale z encodera do decodera) @@ -306,6 +473,8 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **DeepLab v3+:** + Mnemonik: „DZIURY w filtrze" — filtr dosłownie ma dziury (à trous), + przez co widzi dalej bez dodatkowych parametrów. Zwykła konwolucja 3×3: [x][x][x] receptive field = 3 Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5, te same parametry! ASPP: równolegle rate=6,12,18 → multi-scale features → łączenie @@ -313,10 +482,34 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy **Transformery (SegFormer, Mask2Former):** + Mnemonik: „WSZYSCY ze WSZYSTKIMI" — każdy piksel rozmawia z KAŻDYM innym. CNN: filtr 3×3 widzi LOKALNY kontekst (sąsiadów) Transformer: self-attention widzi CAŁY obraz naraz Cena: O(n²) pamięci (n = piksele), ale lepsze wyniki +#### DIY Przykład — U-Net krok po kroku + +Poniższy diagram pokazuje CAŁY pipeline U-Net od obrazu wejściowego do mapy segmentacji. Obraz syntetyczny 64×64 z dwoma obiektami (koła) na jasnym tle. + +![DIY U-Net: obraz → encoder zmniejsza → bottleneck → decoder zwiększa + skip → mapa segmentacji → pseudokod](img/q23_diy_unet.png) + + Pseudokod U-Net (PyTorch-style): + # ENCODER — zmniejsza rozdzielczość, wyciąga cechy + e1 = conv_block(input, filters=64) # [64×64×64] + e2 = conv_block(maxpool(e1), filters=128) # [32×32×128] + e3 = conv_block(maxpool(e2), filters=256) # [16×16×256] + + # BOTTLENECK — najgłębsza warstwa + b = conv_block(maxpool(e3), filters=512) # [8×8×512] + + # DECODER — zwiększa rozdzielczość + skip connections (concat!) + d3 = conv_block(concat(upconv(b), e3), filters=256) # [16×16×256] + d2 = conv_block(concat(upconv(d3), e2), filters=128) # [32×32×128] + d1 = conv_block(concat(upconv(d2), e1), filters=64) # [64×64×64] + + # WYNIK — Conv 1×1 → mapa klas + output = conv_1x1(d1, n_classes=3) # [64×64×3] → argmax → [64×64] etykiety + --- ### Metryki i funkcje kosztu @@ -334,7 +527,38 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy ### Jak zapamiętać -- **U-Net = „U-shape + skip connections"** — encoder-decoder -- **DeepLab = „Atrous (dilated) convolutions + ASPP"** -- **mIoU = Intersection / Union, uśrednione per klasa** +**Super-mnemonik na kolejność algorytmów:** + + „Turyści Oglądają Rzekę, Wodospad, Morze, Nurt — Fotografują Uroczy Dwór Tajemnic" + + Klasyczne: Thresholding → Otsu → Region growing → Watershed → Mean shift → Normalized cuts + Neuronowe: FCN → U-Net → DeepLab → Transformer + +![Mnemoniki: karty z algorytmami segmentacji i ich skojarzeniami](img/q23_mnemonics.png) + +**Mnemoniki per algorytm — STRATEGIE KLASYCZNE:** + +| Algorytm | Mnemonik | Skojarzenie | +|----------|----------|-------------| +| **Thresholding** | „PRÓG na bramce" | Bramkarz przepuszcza piksele > T, blokuje ≤ T | +| **Otsu** | „AUTO-bramkarz" | Sam sprawdza 256 progów, wybiera najlepszy (min σ²) | +| **Region Growing** | „PLAMA atramentu" | Kropla atramentu rozlewa się na podobne piksele (BFS) | +| **Watershed** | „ZALEWANIE terenu" | Woda zalewa doliny, granie gór = granice segmentów | +| **Mean Shift** | „KULKI toczą się do dołków" | Każda kulka → max gęstości, ile dołków = tyle segmentów | +| **Normalized Cuts** | „CIĘCIE sznurków" | Tnij słabe sznurki (krawędzie grafu), zachowaj silne | + +**Mnemoniki per algorytm — SIECI NEURONOWE:** + +| Sieć | Mnemonik | Skojarzenie | +|------|----------|-------------| +| **FCN** | „FC → Conv 1×1" | Otwiera bramkę dla dowolnego rozmiaru wejścia | +| **U-Net** | „Litera U + mosty" | Schodzisz ↓, wracasz ↑, mosty (skip concat) przenoszą detale | +| **DeepLab** | „DZIURY w filtrze" | Filtr ma dziury (à trous) → widzi dalej bez dodatkowych wag | +| **Transformer** | „WSZYSCY ze WSZYSTKIMI" | Każdy piksel pyta każdy inny (self-attention, O(n²)) | + +**Mnemoniki per metrykę:** + +- **mIoU** = „Nakładka / Suma" → intersection / union, uśrednione per klasa +- **Dice** = „Dwie nakładki / Razem" → 2·|A∩B| / (|A|+|B|) +- **Focal** = „Fokus na TRUDNYCH" → trudne piksele ważą więcej diff --git a/pytania/questions/pytanie_24.md b/pytania/questions/pytanie_24.md index eb1407a..666d7b6 100644 --- a/pytania/questions/pytanie_24.md +++ b/pytania/questions/pytanie_24.md @@ -135,17 +135,7 @@ **Architektura CNN — pełny przykład (AlexNet, wygrał ImageNet 2012):** - Obraz [224×224×3] ← 150 528 wartości (piksele RGB) - ↓ Conv1: 96 filtrów 11×11, stride 4 - [55×55×96] ← 96 map cech, każda 55×55 - ↓ MaxPool 3×3, stride 2 - [27×27×96] - ↓ Conv2: 256 filtrów 5×5 - [27×27×256] - ↓ MaxPool → Conv3-5 → MaxPool - [6×6×256] = 9 216 liczb spłaszczonych - ↓ FC(4096) → FC(4096) → FC(1000) → Softmax - → "golden retriever" (klasa 207, pewność 0.89) +![CNN — od obrazu do predykcji](img/q24_cnn_architecture.png) ROZMIARY MALEJĄ: 224 → 55 → 27 → 13 → 6 (kompresja przestrzenna) KANAŁY ROSNĄ: 3 → 96 → 256 → 384 → 256 (coraz więcej wyuczonych cech) @@ -211,14 +201,7 @@ **FPN (Feature Pyramid Network)** — technika łączenia feature map z RÓŻNYCH warstw backbone'u. Wczesne warstwy (wysoka rozdzielczość) → małe obiekty. Późne warstwy (niska rozdzielczość) → duże obiekty. FPN łączy obie → wykrywa obiekty WSZYSTKICH rozmiarów. - Backbone (ResNet): - Warstwa 1: 56×56 → dużo detali, dobre dla MAŁYCH obiektów - Warstwa 2: 28×28 → średnie obiekty - Warstwa 3: 14×14 → duże obiekty - Warstwa 4: 7×7 → bardzo duże obiekty - - FPN: łączy top-down (7×7 → 14×14 → 28×28 → 56×56) + lateral connections - → predykcje na KAŻDYM poziomie → małe I duże obiekty! +![FPN (Feature Pyramid Network)](img/q24_fpn.png) --- @@ -301,15 +284,7 @@ **Support Vectors** — punkty danych NAJBLIŻSZE hiperpłaszczyźnie. To one „podpierają" (support) margines i definiują pozycję hiperpłaszczyzny. Reszta punktów jest nieistotna! Nazwa: „wektory nośne" — bo to wektory cech, które „niosą" decyzję. - Przestrzeń 2D: O = klasa "pie szy" X = klasa "nie-pieszy" - O O - O O - hiperpłaszczyzna → ─ ─ ─ ─ ─ ─ ─ ─ ← margines ↕ - X X - X X X - - Support vectors: O i X najbliższe linii (zaznaczone pogrubione) - SVM: przesuń linię tak, żeby margines ↕ był MAKSYMALNY +![SVM — hiperpłaszczyzna i margines](img/q24_svm_hyperplane.png) **HOG+SVM — klasyczny pipeline detekcji pieszych:** @@ -326,15 +301,7 @@ **Haar features (cechy Haarowe)** — najprostsze cechy obrazowe: prostokąty podzielone na jasną i ciemną część. Wartość cechy = (suma pikseli jasnych) − (suma pikseli ciemnych). Proste, ale skuteczne — wykrywają kontrasty typowe dla twarzy. - Przykłady cech Haar: - Krawędź pionowa: Krawędź pozioma: Linia (3 prostokąty): - ┌──────┬──────┐ ┌────────────┐ ┌────┬──────┬────┐ - │JASNY │CIEMNY│ │ JASNY │ │CIEM│JASNY │CIEM│ - │ +Σ₁ │ -Σ₂ │ │ +Σ₁ │ │ -Σ₁│ +Σ₂ │ -Σ₃│ - │ │ │ ├────────────┤ │ │ │ │ - └──────┴──────┘ │ CIEMNY │ └────┴──────┴────┘ - wartość = Σ₁ − Σ₂ │ -Σ₂ │ wartość = Σ₂ − Σ₁ − Σ₃ - └────────────┘ +![Cechy Haar — typy i zastosowanie na twarzy](img/q24_haar_features.png) Dlaczego działa na TWARZACH? - Oczy CIEMNIEJSZE niż czoło → cecha "krawędź pozioma" daje dużą wartość @@ -348,14 +315,7 @@ Jak? Integral Image[x,y] = suma WSZYSTKICH pikseli od (0,0) do (x,y). - Obraz oryginalny: Integral Image (sumy kumulatywne): - [1 2 3] [ 1 3 6] - [4 5 6] [ 5 12 21] - [7 8 9] [12 27 45] - - Chcemy sumę prostokąta (1,1)-(2,2) = piksele [5,6,8,9] = 28: - Z Integral Image: II[2,2] − II[0,2] − II[2,0] + II[0,0] - = 45 − 6 − 12 + 1 = 28 ✓ +![Integral Image — suma prostokąta w O(1)](img/q24_integral_image.png) Zawsze 4 odczyty z tabeli → O(1)! Czy prostokąt ma 4 piksele czy 4 MILIONY — czas TEN SAM! @@ -389,17 +349,7 @@ **Cascade (kaskada klasyfikatorów)** — genialna optymalizacja szybkości: zamiast sprawdzać WSZYSTKIE 200 cech na każdym oknie, użyj KASKADY etapów. Każdy etap = prosty klasyfikator, który szybko ODRZUCA "na pewno nie-twarz". - Etap 1: 2 cechy → odrzuca 50% okien (czas: ~1 μs) - Etap 2: 10 cech → odrzuca 80% reszty (czas: ~5 μs) - Etap 3: 25 cech → odrzuca 90% reszty - ... - Etap 25: 200 cech → szczegółowa analiza (czas: ~100 μs) - - Sliding window: ~500 000 okien do sprawdzenia (różne pozycje × skale) - BEZ kaskady: 500 000 × 200 cech = WOLNO - Z kaskadą: 99% okien odrzuconych w etapach 1-3 (za ~5μs każde!) - Tylko 0.01% dochodzi do etapu 25 - → CAŁY obraz w ~30ms = 30+ fps = REAL-TIME! +![Viola-Jones — kaskada klasyfikatorów (SITO)](img/q24_viola_jones_cascade.png) Mnemonik: kaskada = "SITO" — coraz drobniejsze oczka, na początku odpada piach, na końcu zostaje ZŁOTO (twarz). @@ -414,7 +364,7 @@ --- -![Ewolucja detektorów: R-CNN → Faster R-CNN → YOLO](img/rcnn_evolution.png) +![Ewolucja detektorów: R-CNN → Faster R-CNN → YOLO](img/q24_rcnn_evolution.png) **R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. Nazwa: Region-based CNN. @@ -467,13 +417,7 @@ 3. W każdej komórce weź MAX (jak max pooling) 4. Wynik: tensor 7×7 — STAŁY rozmiar niezależnie od oryginalnego ROI! - Przykład (ROI Pool 2×2 dla prostoty): - ROI na feature mapie [4×4]: Po ROI Pool 2×2: - [1 3 | 2 1] [5 6] ← max(1,3,0,5)=5 max(2,1,1,6)=6 - [0 5 | 1 6] [7 9] ← max(0,4,7,2)=7 max(1,0,9,1)=9 - ───────────── - [0 4 | 1 0] - [7 2 | 9 1] +![ROI Pooling](img/q24_roi_pooling.png) Kluczowa sztuczka Fast R-CNN: CNN raz na CAŁY obraz → JEDNA feature mapa → ROI Pool 2000 regionów z TEJ SAMEJ mapy @@ -544,22 +488,14 @@ - C prawdopodobieństw klas = „jaki to obiekt?" Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps! - Jak to działa wizualnie (S=7, B=2, C=20 klas jak w Pascal VOC): - - Obraz [448×448] → CNN (24 warstwy konwolucyjne + 2 FC) → tensor 7×7×30 - ↑ - 30 = 2×(4+1) + 20 - 2 bbox × (x,y,w,h,conf) + 20 klas - - Komórka (3,4) predykuje: bbox1=(0.3, 0.7, 0.4, 0.6, 0.92), klasa="samochód" (p=0.88) - → „środek samochodu jest w komórce (3,4), bbox ma takie wymiary, pewność 92%" - - Potem NMS: usuwa duplikaty (wiele komórek może wykryć ten sam obiekt) +![YOLO — detekcja jednoetapowa (siatka S×S)](img/q24_yolo_grid.png) **SSD (Single Shot MultiBox Detector, 2016)** — ulepsza YOLO przez multi-scale feature maps: predykcje z WIELU warstw CNN, każda o innej rozdzielczości. Wczesne warstwy (wysoka rozdzielczość) wykrywają MAŁE obiekty; późne warstwy (niska rozdzielczość) wykrywają DUŻE. Anchor boxes predefiniowane na każdej skali. **Anchor box (kotwica)** — predefiniowany prostokąt o określonym kształcie/proporcji (np. 1:1, 1:2, 2:1). Sieć NIE predykuje bbox od zera — predykuje PRZESUNIĘCIE (offset) od najbliższego anchora. Łatwiejsze zadanie! Wiele anchorów → pokrycie różnych kształtów obiektów (osoby = wysoki prostokąt, samochód = szeroki). +![Anchor boxes — predefiniowane kształty](img/q24_anchor_boxes.png) + **Anchor-free** — nowoczesne podejście (FCOS, YOLOv8): bezpośrednia predykcja środka i wymiarów, bez predefiniowanych anchorów. Prostsza architektura, mniej hyperparametrów. **Transformer** — architektura sieci neuronowej pierwotnie z NLP (2017, "Attention is All You Need"), ale skutecznie zaadaptowana do wizji komputerowej (ViT, DETR). Kluczowy mechanizm: **self-attention** — każdy element wejścia "patrzy" na WSZYSTKIE inne elementy i decyduje, które są dla niego ważne. @@ -587,10 +523,7 @@ Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps! **DETR (DEtection TRansformer, 2020)** — model Facebooka stosujący Transformer do detekcji. Radykalnie prostszy pipeline: BRAK anchorów, BRAK NMS! Sieć predykuje bezpośrednio ZESTAW N obiektów (np. N=100). - Pipeline DETR: - Obraz → CNN backbone → Feature mapa → Transformer Encoder (self-attention) - → Transformer Decoder (z N=100 "object queries") - → N predykcji: [(klasa₁, bbox₁), ..., (klasa₁₀₀, bbox₁₀₀)] +![DETR — Transformer do detekcji](img/q24_detr_pipeline.png) "Object queries" = 100 wyuczonych wektorów, każdy "szuka" jednego obiektu. Obraz z 5 obiektami → 5 queries dopasuje się do obiektów, @@ -655,13 +588,7 @@ Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps! **IoU (Intersection over Union)** — miara nakładania dwóch prostokątów. IoU = pole przecięcia / pole sumy. Wartości: 0.0 (nie nakładają się) do 1.0 (identyczne). - bbox A: bbox B: Przecięcie: - ┌──────────┐ ┌────┐ - │ A │ ┌──────────┐ │ ∩ │ - │ ┌───┼────────┼──┐ │ └────┘ - │ │ ∩ │ │ │ B │ - └──────┼───┘ │ │ │ - └────────────┴──┘ +![IoU (Intersection over Union)](img/q24_iou_diagram.png) IoU = pole(∩) / pole(A ∪ B) = pole(∩) / (pole(A) + pole(B) − pole(∩)) @@ -696,11 +623,7 @@ Detekcja obiektów to **lokalizacja** (gdzie?) i **klasyfikacja** (co?) obiektó **Porównanie z innymi zadaniami:** - Zadanie Wynik Przykład - ───────────────────────────────────────────────────────── - Klasyfikacja "kot" (1 etykieta) cały obraz → 1 klasa - Detekcja bbox + klasa (N obiektów) prostokąty wokół obiektów - Segmentacja etykieta per piksel maska pikseli +![Klasyfikacja vs Detekcja vs Segmentacja](img/q24_detection_tasks.png) --- @@ -713,21 +636,134 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc | **HOG + SVM** | 2005 | Histogram of Oriented Gradients | SVM | wolna (~1 fps) | detekcja pieszych | | **Viola-Jones** | 2001 | Haar features + Integral Image | AdaBoost cascade | real-time (30+ fps) | detekcja twarzy | -**HOG + SVM (Dalal & Triggs, 2005):** +#### HOG + SVM (Dalal & Triggs, 2005) — krok po kroku - Pipeline: Obraz → Sliding window → HOG (histogramy gradientów) → SVM → detekcja/brak - HOG: dzieli okno na komórki (8×8 px), liczy histogramy kierunków krawędzi - SVM: "czy ten wzorzec krawędzi to człowiek?" - Wada: ręczne cechy, wolny sliding window, działa dobrze TYLKO na pieszych +**Mnemonik kroków HOG: „GÓRA KOCHA BOGATYCH NARCIARZY" → Gradienty → Orientacja → Komórki → Bloki → Normalizacja** -**Viola-Jones (2001) — 3 innowacje:** +![HOG + SVM pipeline detekcji pieszych](img/q24_hog_svm_pipeline.png) - 1. Haar features: [ jasne | ciemne ] → prosta różnica intensywności - 2. Integral Image: suma prostokąta w O(1), niezależnie od rozmiaru! - 3. Cascade: Etap 1 (2 cechy): odrzuca 50% okien w 1 μs - Etap 2 (10 cech): odrzuca 80% reszty - ...Etap 25 (200 cech): szczegółowa analiza TYLKO 0.01% okien - Efekt: ~95% detections = szybkie odrzucenia → real-time! +**Krok 1 — Gradienty (G jak GÓRA):** Oblicz gradient KAŻDEGO piksela. Gradient = „siła i kierunek zmiany jasności". Tam, gdzie jasność skacze (np. 50→200), jest krawędź. + + Przykład liczbowy: + Piksele w wierszu: [50, 50, 200] + Gx = pixel[x+1] − pixel[x−1] = 200 − 50 = 150 ← silna krawędź pionowa! + Gy = analogicznie w pionie + Siła: magnitude = √(Gx² + Gy²) = √(150² + 0²) = 150 + Kierunek: direction = arctan(Gy/Gx) = arctan(0/150) = 0° (krawędź pionowa) + +**Krok 2 — Orientacja (O jak KOCHA):** Każdy piksel głosuje na kierunek swojej krawędzi. 9 „koszyków" (binów) co 20°: 0°, 20°, 40°, …, 160°. Głos ważony SIŁĄ gradientu (silniejsza krawędź = mocniejszy głos). + + Piksel z magnitude=150, direction=10°: + Głosuje na bin 0° (z wagą proporcjonalną do bliskości) i bin 20° + Piksel z magnitude=30, direction=85°: + Głosuje na bin 80° i bin 100° (słabsza krawędź = słabszy głos) + +**Krok 3 — Komórki (K jak BOGATYCH):** Podziel okno (64×128 px) na komórki 8×8 pikseli = 8×16 = 128 komórek. Dla KAŻDEJ komórki stwórz histogram 9 binów — to jej „odcisk palca kierunkowości krawędzi". + +![HOG — kroki obliczania cech](img/q24_hog_gradient_steps.png) + +**Krok 4 — Bloki (B jak NARCIARZY):** Grupuj komórki w bloki 2×2 (= 16×16 px). Przesuwaj blok z krokiem 1 komórki. Okno 64×128 → (8−1)×(16−1) = 7×15 = 105 bloków. + +**Krok 5 — Normalizacja (N):** Dla KAŻDEGO bloku (4 komórki × 9 binów = 36 wartości) wykonaj normalizację L2 → odporność na zmiany oświetlenia. 105 bloków × 36 = **3780 cech** → wektor HOG. + + Pseudokod: + def compute_hog(window_64x128): + Gx = pixel[x+1] - pixel[x-1] # gradient poziomy + Gy = pixel[y+1] - pixel[y-1] # gradient pionowy + mag = sqrt(Gx**2 + Gy**2) # siła + dir = arctan2(Gy, Gx) * 180 / pi # kierunek 0°-180° + + hog = [] + for block_2x2 in sliding_blocks(cells_8x8): + block_hist = [] + for cell in block_2x2: # 4 komórki + hist = [0]*9 # 9 binów + for px in cell.pixels: # 64 piksele + bin = int(dir[px] / 20) # który bin? + hist[bin] += mag[px] # ważone głosowanie + block_hist += hist + block_hist = L2_normalize(block_hist) # normalizacja! + hog += block_hist + return hog # wektor 3780 cech → do SVM + +**Krok 6 — SVM klasyfikuje:** Wektor 3780 cech → SVM odpowiada: „pieszy" (+1) lub „tło" (−1). + +![SVM — hiperpłaszczyzna i margines](img/q24_svm_hyperplane.png) + + Mnemonik SVM: „LINIA MAKSYMALNEGO ODDECHU" + SVM = linia (hiperpłaszczyzna) z MAKSYMALNYM marginesem. + Jak MOST nad rzeką — im szerszy, tym bezpieczniejszy (lepiej generalizuje). + +**Krok 7 — NMS:** Usuń duplikaty (wiele okien wykryło tego samego pieszego → zachowaj najlepsze). + + Mnemonik PEŁNEGO pipeline'u HOG+SVM: „GOKBN-SN" + → Gradienty → Orientacja → Komórki → Bloki → Normalizacja → SVM → NMS + = „Grasz Ostro, Kumplu? Bądź Naturalny, Szybko Nabierz (wprawy)!" + +--- + +#### Viola-Jones (2001) — krok po kroku + +**Mnemonik 3 innowacji: „HIC" → Haar + Integral Image + Cascade** + +**Innowacja 1 — Haar features (H):** Prostokąty dzielone na jasną i ciemną część. Wartość = Σ(jasna) − Σ(ciemna). Proste, ale wykrywają kontrasty typowe dla twarzy. + +![Cechy Haar — typy i zastosowanie na twarzy](img/q24_haar_features.png) + + Pseudokod cechy Haar: + def haar_edge_vertical(img, x, y, w, h): + left_sum = sum_pixels(img, x, y, x+w//2, y+h) # jasna połówka + right_sum = sum_pixels(img, x+w//2, y, x+w, y+h) # ciemna połówka + return left_sum - right_sum # duża wartość = silna krawędź + + Mnemonik: Haar = „Hej, A tu jest Różnica?" + Cechy Haar pytają: „Czy lewa strona JAŚNIEJSZA niż prawa?" + +**Innowacja 2 — Integral Image (I):** Precomputed tabela: suma DOWOLNEGO prostokąta w O(1) — 4 odczyty z tabeli, niezależnie od rozmiaru! + +![Integral Image — suma prostokąta w O(1)](img/q24_integral_image.png) + + Pseudokod: + def build_integral_image(img): + II = zeros(H, W) + for y in range(H): + for x in range(W): + II[y][x] = img[y][x] + II[y-1][x] + II[y][x-1] - II[y-1][x-1] + return II + + def rect_sum(II, x1, y1, x2, y2): # ZAWSZE O(1)! + return II[y2][x2] - II[y1-1][x2] - II[y2][x1-1] + II[y1-1][x1-1] + + Mnemonik: Integral Image = „4 Odczyty I Gotowe!" = 4OIG + Jak czytanie z gotowej tabeli: nie liczymy, tylko odczytujemy! + +**Innowacja 3 — Cascade (C):** Kaskada etapów — szybkie odrzucanie „na pewno nie-twarz". + +![Viola-Jones — kaskada klasyfikatorów (SITO)](img/q24_viola_jones_cascade.png) + + Pseudokod: + def cascade_classify(window): + for stage in [stage_1, stage_2, ..., stage_25]: + score = sum(stage.weights[i] * haar_feature[i](window) + for i in stage.features) + if score < stage.threshold: + return "NIE-TWARZ" # szybkie odrzucenie! + return "TWARZ" # przeszło WSZYSTKIE etapy + + Mnemonik: Cascade = „SITO z coraz drobniejszymi oczkami" + Etap 1: sito o dużych oczkach → odpada piach (oczywiste nie-twarze) + Etap 25: sito najdrobniejsze → zostaje ZŁOTO (twarz) + 99% okien odpada w pierwszych 3 etapach → REAL-TIME! + +**Pełny pipeline Viola-Jones:** + + 1. Sliding window (24×24) po obrazie w wielu skalach + 2. Integral Image (preprocessing, O(n) — raz) + 3. Dla każdego okna: kaskada (Haar + AdaBoost, najczęściej odrzuci w 1-3 etapie) + 4. NMS na detekcjach → wynik + + Mnemonik pipeline'u: „SIKN" = Sliding → Integral → Kaskada → NMS + = „Szybko Identyfikuj Kształty Niezwykłe!" --- @@ -746,6 +782,8 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc Fast R-CNN: [CNN raz] → [ROI Pool 2000 regionów] → [FC] = 2s lepiej Faster R-CNN:[CNN] → [RPN generuje propozycje] → [ROI Pool] → [FC] = 0.2s! +![Ewolucja detektorów: R-CNN → Faster R-CNN](img/q24_rcnn_evolution.png) + **One-stage detectors (jednoetapowe)** — klasyfikacja i lokalizacja w JEDNYM przejściu. | Model | Rok | Szybkość | Innowacja | @@ -763,13 +801,7 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc **Two-stage vs One-stage:** - Cecha Two-stage (Faster R-CNN) One-stage (YOLO) - ───────────────────────────────────────────────────────────────── - Szybkość ~5 fps 45-155 fps - Dokładność (mAP) wyższa (historycznie) dorównuje (YOLOv8) - Małe obiekty lepszy gorszy (ale SSD/FPN pomaga) - Architektura 2 etapy + NMS 1 etap + NMS (DETR: bez NMS) - Real-time? nie TAK +![Two-stage vs One-stage — porównanie](img/q24_two_vs_one_stage.png) --- @@ -777,49 +809,205 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak go użyć do **lokalizacji** obiektów? -**Podejście 1 — Sliding Window (najwolniejsze):** +**Mnemonik 3 podejść: „SRF" = „Sliding → Region → Fine-tune" = „Szukaj Ręcznie, Finalnie optymalizuj!"** - Wytnij okno → klasyfikuj → przesuń → powtórz → NMS - Obraz 640×480, okno 64×64, krok 8px, 5 skal: - ~240 000 pozycji × 5 skal = ~1 200 000 klasyfikacji! - Przy 100 cls/sec → 3.3 godziny na 1 obraz → NIEPRAKTYCZNE +![Jak zbudować detektor z klasyfikatora? — 3 podejścia](img/q24_detector_from_classifier.png) -**Podejście 2 — Region Proposals + Klasyfikator (szybsze):** +--- - Selective Search → ~2000 regionów (zamiast milionów) - Każdy region → resize → klasyfikator → wynik + NMS - Przy 100 cls/sec → 20 sec/obraz → lepiej, ale wciąż wolno - To jest dokładnie R-CNN (2014) +#### Podejście 1 — Sliding Window (najprostsze, NAJWOLNIEJSZE) -**Podejście 3 — Fine-tune backbone + detection head (najlepsze):** +**Idea:** Wycinaj prostokątne fragmenty obrazu, KAŻDY pokaż klasyfikatorowi, zbierz pozytywne. - Pretrained classifier (ResNet): obraz → cechy → FC → "kot" - Zamień FC na detection head: - obraz → cechy (backbone) → [cls head: P(klasa)] - → [bbox head: Δx, Δy, Δw, Δh] - Dotrenuj na danych z bounding boxami (COCO, VOC) - = Transfer learning → NAJLEPSZA jakość + szybkość - To jest Faster R-CNN, YOLO, SSD — wszystkie używają pretrained backbone! +**Mnemonik: „WYCINAJ i PYTAJ" — jak wycinanie ciasteczek: koło po kole, aż cały obraz pokryty.** - Podsumowanie: - Sliding Window: ~milion klasyfikacji → NIEPRAKTYCZNE - Region Proposals: ~2000 klasyfikacji → wolne ale działa (R-CNN) - Fine-tune: 1 przejście sieci → szybkie i dokładne (Faster R-CNN, YOLO) +![Sliding Window — najprostsze podejście](img/q24_sliding_window.png) + + Pseudokod: + def sliding_window_detect(image, classifier, window_size=64, step=8): + detections = [] + for scale in [0.5, 0.75, 1.0, 1.5, 2.0]: # 5 skal + resized = resize(image, scale) + for y in range(0, resized.height - window_size, step): + for x in range(0, resized.width - window_size, step): + window = resized[y:y+window_size, x:x+window_size] + label, confidence = classifier.predict(window) + if label != "tło" and confidence > 0.5: + # przelicz współrzędne na oryginał + bbox = (x/scale, y/scale, + (x+window_size)/scale, (y+window_size)/scale) + detections.append((label, bbox, confidence)) + return nms(detections) # usuń duplikaty + +**Dlaczego wiele skal?** Obiekty mają różne rozmiary — kot blisko = duży, kot daleko = mały. Okno 64×64 nie złapie kota 200×200. + + Obliczenia dla obrazu 640×480: + Pozycje na skali 1.0: (640-64)/8 × (480-64)/8 = 72 × 52 = 3 744 + × 5 skal = 18 720 okien + × klasyfikacja ResNet (~10ms/obraz na GPU) = ~3 minuty + × na CPU (~100ms/obraz) = ~30 minut na 1 obraz! + ⚠ NIEPRAKTYCZNE dla zastosowań real-time + +**Wady:** (1) Ekstremalnie wolne. (2) Stały kształt okna — obiekty nie są kwadratowe. (3) ~99.9% okien to „tło" → marnowanie czasu. + +--- + +#### Podejście 2 — Region Proposals + Klasyfikator (= R-CNN) + +**Idea:** Zamiast milionów okien, inteligentnie zaproponuj ~2000 regionów, w których MOGĄ być obiekty, i tylko te sklasyfikuj. + +**Mnemonik: „INTELIGENTNE CIĘCIE" — zamiast kroić cały tort na milion kawałków, wytnij tylko tam, gdzie widzisz wiśnie (obiekty).** + + Pseudokod (= R-CNN): + def region_proposal_detect(image, classifier): + # Krok 1: Selective Search — inteligentnie generuj regiony + proposals = selective_search(image) # ~2000 prostokątów + detections = [] + + # Krok 2: Dla KAŻDEGO regionu — clasificuj + for bbox in proposals: # ~2000 iteracji (nie milion!) + crop = image[bbox] # wytnij region + crop = resize(crop, 224, 224) # rozmiar wymagany przez CNN + features = cnn_backbone(crop) # ResNet → wektor 2048 cech + label, conf = svm_classify(features) # SVM: "samochód? kot? tło?" + if label != "tło" and conf > 0.5: + detections.append((label, bbox, conf)) + + # Krok 3: bbox regression — doprecyzuj pozycje + for det in detections: + det.bbox += bbox_regressor(det.features) # Δx, Δy, Δw, Δh + + return nms(detections) # Krok 4: usuń duplikaty + +**Dlaczego 2000 a nie milion?** Selective Search łączy podobne fragmenty obrazu (kolor, tekstura) bottom-up. Wynik: ~2000 „mądrych" propozycji, z których ~50% zawiera coś (vs 0.1% w sliding window). + + Porównanie z sliding window: + Sliding Window: ~18 000 okien × 10ms = ~3 min + Proposals: ~2 000 regionów × 10ms = ~20 sec ← 9× szybciej + ALE wciąż 2000 × forward pass CNN → dlatego powstał Fast R-CNN! + +**Wady:** (1) Selective Search jest osobnym algorytmem (nie end-to-end). (2) 2000 × forward pass CNN = wciąż wolno. (3) SVM trenowany OSOBNO od CNN. + +--- + +#### Podejście 3 — Fine-tune backbone + detection head (NAJLEPSZE) + +**Idea:** Weź pretrenowany klasyfikator, ODETNIJ głowicę klasyfikacyjną (FC 1000 klas), zastąp ją DWOMA nowymi głowicami: (1) głowica klasyfikacji → klasa obiektu, (2) głowica regresji → pozycja bbox. + +**Mnemonik: „PRZESZCZEP GŁOWY" — ten sam silnik (backbone), nowa głowa (detection head).** + + Pseudokod (= Faster R-CNN / YOLO w uproszczeniu): + # KROK 1: Weź pretrenowany klasyfikator + resnet = load_pretrained("resnet50_imagenet") # 1000 klas ImageNet + + # KROK 2: Odetnij starą głowicę klasyfikacji + backbone = resnet.layers[:-2] # ZACHOWAJ: Conv1...Conv5 (ekstraktor cech) + # WYRZUĆ: FC(1000) + Softmax + + # KROK 3: Dodaj nowe głowice detekcji + class DetectionHead: + def __init__(self): + self.cls_head = Linear(2048, num_classes) # "samochód? kot? tło?" + self.bbox_head = Linear(2048, 4) # Δx, Δy, Δw, Δh + + def forward(self, features): + cls = softmax(self.cls_head(features)) # P(klasa) + bbox = self.bbox_head(features) # przesunięcie bbox + return cls, bbox + + # KROK 4: Zamroź backbone, trenuj głowice na danych detekcyjnych + for image, gt_boxes, gt_labels in coco_dataset: + features = backbone(image) # pretrenowane cechy (zamrożone) + cls, bbox = detection_head(features) + loss = cls_loss(cls, gt_labels) + bbox_loss(bbox, gt_boxes) + loss.backward() # aktualizuj TYLKO detection_head + + # KROK 5 (opcja): Fine-tune — odmroź backbone z MAŁYM learning rate + backbone.unfreeze() + optimizer = SGD(lr=0.0001) # 10× mniejszy niż dla głowicy! + # trenuj jak w kroku 4, ale teraz backbone też się uczy + +**Dlaczego to działa?** Pretrenowany backbone na ImageNet „wie", jak wyglądają krawędzie, tekstury, kształty. Te cechy są UNIWERSALNE — przydają się zarówno do klasyfikacji „złota rybka vs samolot" jak i do detekcji „samochód na zdjęciu z drona". + + Transfer learning w liczbach: + Trenowanie od zera na COCO (330K obrazów): ~12h na 8×V100 GPU + Fine-tune pretrained ResNet-50: ~4h na 8×V100 GPU ← 3× szybciej! + Fine-tune osiąga mAP ~42%, od zera ~38% ← lepsze wyniki! + +**Pełny przykład w PyTorch (Faster R-CNN z pretrained backbone):** + + import torchvision + from torchvision.models.detection import fasterrcnn_resnet50_fpn + + # Gotowy detektor z pretrained backbone! + model = fasterrcnn_resnet50_fpn(pretrained=True) + + # Custom: zmiana na 5 klas (zamiast 91 COCO) + num_classes = 5 # 4 obiekty + tło + in_features = model.roi_heads.box_predictor.cls_score.in_features + model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) + + # Trening: + model.train() + for images, targets in dataloader: + loss_dict = model(images, targets) # cls_loss + bbox_loss + total_loss = sum(loss_dict.values()) + total_loss.backward() + optimizer.step() + + # Inferencja: + model.eval() + predictions = model([test_image]) + # predictions = [{'boxes': tensor, 'labels': tensor, 'scores': tensor}] + # boxes = [[x1,y1,x2,y2], ...], labels = [1, 3, ...], scores = [0.95, 0.88, ...] + +--- + +#### Podsumowanie — porządek od NAJGORSZEGO do NAJLEPSZEGO: + + Podejście Okien Czas/obraz Jakość Rok Przykład + ────────────────────────────────────────────────────────────────────────── + Sliding Window ~milion ~30 min niska - (teoria) + Region Proposals ~2000 ~20-50 sec średnia 2014 R-CNN + Fine-tune + RPN ~300 ~0.2 sec wysoka 2015 Faster R-CNN + One-stage 1×siatka ~7-22 ms wysoka 2016+ YOLO, SSD + Transformer N queries ~25 ms wysoka 2020 DETR + + Mnemonik porządku: „SRFTD" = „Sliding → Region → Fine-tune → Transformer → (Done!)" + = „Szukaj Ręcznie, Finalnie Transformer (Detekuje!)" --- ### NMS (Non-Maximum Suppression) — post-processing +![NMS — usuwanie duplikatów](img/q24_nms_steps.png) + Detektor generuje WIELE nakładających się bbox dla jednego obiektu: [bbox1, 0.95], [bbox2, 0.90], [bbox3, 0.85] — wszystkie na tym samym kocie - Algorytm NMS: - 1. Sortuj po confidence: [0.95, 0.90, 0.85] - 2. Weź najlepszą (0.95) → ZACHOWAJ - 3. Oblicz IoU z resztą: IoU(bbox1,bbox2)=0.82, IoU(bbox1,bbox3)=0.75 - 4. Usuń te z IoU > próg (0.5): usuń bbox2 i bbox3 - 5. Powtórz dla następnej najlepszej - Wynik: 1 bbox per obiekt + Pseudokod NMS: + def nms(detections, iou_threshold=0.5): + detections.sort(by=confidence, descending=True) + keep = [] + while detections: + best = detections.pop(0) # weź najlepszą + keep.append(best) # ZACHOWAJ + detections = [d for d in detections + if iou(best, d) < iou_threshold] # usuń nakładające + return keep + + Krok po kroku (przykład): + 1. Sortuj: [0.95, 0.90, 0.85, 0.40] + 2. Weź bbox₁ (0.95) → ZACHOWAJ + 3. IoU(bbox₁, bbox₂) = 0.82 > 0.5 → USUŃ (duplikat!) + IoU(bbox₁, bbox₃) = 0.75 > 0.5 → USUŃ (duplikat!) + IoU(bbox₁, bbox₄) = 0.10 < 0.5 → ZACHOWAJ (INNY obiekt!) + 4. Wynik: [bbox₁, bbox₄] — 2 unikalne obiekty + +![IoU (Intersection over Union)](img/q24_iou_diagram.png) + + Mnemonik NMS: „Najlepszy Ma Się dobrze" — zachowaj najlepszą, resztę wyrzuć + Mnemonik IoU: „Ile pokrycia Ustalono?" — pole(∩) / pole(A∪B) ### Etymologia @@ -832,10 +1020,15 @@ Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak g - **FC = „Full Connection"** — każdy z każdym, warstwa decyzyjna na końcu CNN - **Backbone = SILNIK samochodu** — ten sam silnik (ResNet), różne karoserie (klasyfikacja/detekcja/segmentacja) - **Backbone'y: A→V→R = „Architektura Bardzo Rezylientna"** — AlexNet (2012) → VGG (2014) → ResNet (2015) -- **Transfer learning** — nie ucz się od zera, przenieś wiedzę z ImageNet -- **Viola-Jones: kaskada = „SITO"** — piach odpada wcześnie, złoto (twarz) zostaje na końcu +- **Transfer learning = „PRZESZCZEP GŁOWY"** — nie ucz się od zera, przenieś wiedzę z ImageNet, zmień głowicę +- **HOG kroki: „GOKBN" = „Grasz Ostro, Kumplu? Bądź Naturalny"** — Gradienty → Orientacja → Komórki → Bloki → Normalizacja +- **SVM = „LINIA MAKSYMALNEGO ODDECHU"** — margines jak most: im szerszy, tym bezpieczniej +- **Viola-Jones: „HIC" = Haar + Integral Image + Cascade** +- **Haar = „Hej, A tu jest Różnica?"** — porównuje jasne i ciemne prostokąty +- **Integral Image = „4 Odczyty I Gotowe" (4OIG)** — suma dowolnego prostokąta O(1) +- **Kaskada = „SITO"** — piach odpada wcześnie, złoto (twarz) zostaje na końcu +- **Viola-Jones pipeline: „SIKN" = „Szybko Identyfikuj Kształty Niezwykłe"** — Sliding → Integral → Kaskada → NMS - **AdaBoost = „ADAptacyjnie BOOSTuj"** — słabe modele razem = silny -- **Integral Image** — 4 odczyty = suma dowolnego prostokąta, zawsze O(1) - **Selective Search** — inteligentne łączenie regionów zamiast milionów okien - **ROI Pooling** — dowolny rozmiar → stały rozmiar (siatkowanie + max) - **Bbox regression = „GPS korekta"** — popraw przybliżoną pozycję o Δx, Δy, Δw, Δh @@ -843,6 +1036,7 @@ Masz wytrenowany klasyfikator (np. ResNet na ImageNet: obraz → „kot"). Jak g - **YOLO = „You Only Look Once"** — jednoetapowy, szybki, siatka S×S - **Faster R-CNN = CNN + RPN + ROI Pool** — dwuetapowy, dokładny - **NMS = „Najlepszy Ma Się dobrze"** — zachowaj najlepszą detekcję, usuń duplikaty +- **IoU = „Ile pokrycia Ustalono?"** — pole(∩) / pole(A∪B) - **DETR = „Detekcja Eliminująca Trikowe Redundancje"** — bez NMS, bez anchorów, transformer -- **Detektor z klasyfikatora:** sliding window (wolno) → proposals (lepiej) → fine-tune backbone (najlepiej) → DETR (najprościej) +- **Detektor z klasyfikatora: „SRF" = „Szukaj Ręcznie, Finalnie optymalizuj!"** — Sliding Window (wolno) → Region Proposals (lepiej) → Fine-tune backbone (najlepiej) diff --git a/pytania/questions/pytanie_31.md b/pytania/questions/pytanie_31.md index ba376f9..efffe48 100644 --- a/pytania/questions/pytanie_31.md +++ b/pytania/questions/pytanie_31.md @@ -61,15 +61,16 @@ │ → NIE → wróć do 1│ └──────────────────────────────────────────┘ -**Metody interaktywne (interactive methods)** — konkretne algorytmy realizujące interaktywne wspomaganie decyzji. W kontekście tego pytania są to: metoda loterii (wyznaczanie funkcji użyteczności U(x) przez pytania o loterie), metoda certainty equivalent (wyznaczanie ekwiwalentu pewności), AHP (porównania parami), PROMETHEE i ELECTRE (metody outranking). Każda z nich wymaga od decydenta ODPOWIEDZI na pytania — to czyni je interaktywnymi. +**Metody interaktywne (interactive methods)** — konkretne algorytmy realizujące interaktywne wspomaganie decyzji. W kontekście tego pytania są to KRYTERIA DECYZYJNE stosowane gdy decydent nie zna prawdopodobieństw stanów natury (lub je zakłada). Interaktywność polega na tym, że decydent WYBIERA kryterium (a w przypadku Hurwicza — także parametr α), co wymaga dialogu o jego postawie wobec ryzyka. - Metoda Jakie pytania zadaje decydentowi? + Kryterium Pytanie do decydenta / założenie ────────────────────────────────────────────────────────────────── - Loteria „Wolisz X na pewno, czy loterię (p: best, 1-p: worst)?" - CE „Ile na pewno = ta loteria?" - AHP „Ile razy kryterium A ważniejsze od B?" (skala 1-9) - PROMETHEE „Jak ważne jest każde kryterium?" (wagi) - ELECTRE „Jaki próg zgody/sprzeciwu?" + Wart. oczekiwana „Znasz prawdopodobieństwa stanów?" (potrzebne p) + Laplace'a „Każdy stan natury równie prawdopodobny" (założenie) + Optymistyczne „Zawsze liczysz na najlepszy scenariusz?" (postawa) + Pesymistyczne „Chcesz zabezpieczyć się przed najgorszym?" (postawa) + Hurwicza „Podaj swój współczynnik optymizmu α ∈ [0,1]" (parametr) + Savage'a „Chcesz minimalizować żal z podjętej decyzji?" (postawa) --- @@ -81,101 +82,234 @@ Przykład ryzyka: „Z 60% szansą zysk 100 zł, z 40% strata 50 zł." Przykład niepewności: „Możemy zyskać lub stracić, ale nie wiemy ile i z jakim prawdopodobieństwem." ---- - -**Decydent (decision maker)** — osoba lub podmiot, który musi wybrać jedną z dostępnych alternatyw. Metody interaktywne wymagają dialogu z decydentem — pytamy go o preferencje, zamiast zakładać je z góry. - -**Funkcja użyteczności U(x) (utility function)** — matematyczne przypisanie „wartości subiektywnej" do wyniku. Dla kogoś, kto boi się ryzyka, różnica między 0 a 1000 zł jest bardziej odczuwalna niż między 9000 a 10000 zł. - - U(x) - │ ╭──────── wklęsła (risk-averse) - │ ╱╱ - │ ╱╱ - │╱╱ - └──────────── x (pieniądze) - -**Risk averse (awersja do ryzyka)** — decydent preferuje pewne wyniki nad ryzykowne loterie o tej samej wartości oczekiwanej. Funkcja U jest **wklęsła** (concave): U''(x) < 0. - - Loteria: 50% szans na 0 zł, 50% na 100 zł → E[X] = 50 zł - Risk-averse: „Wolę 50 zł na pewno" (a nawet 40 zł na pewno!) - -**Risk neutral (neutralność)** — U jest liniowa. Decydentowi jest obojętne czy dostanie E[X] na pewno, czy zagra w loterię. - -**Risk seeking (skłonność do ryzyka)** — U jest **wypukła** (convex). Decydent woli ryzyko niż pewny E[X]. „Wolę zagrać niż dostać pewniaka." +![Warunki decyzyjne — spektrum wiedzy decydenta](img/q31_conditions_spectrum.png) --- -**Loteria (lottery)** — formalizacja decyzji ryzykownej: zbiór wyników z ich prawdopodobieństwami. Notacja L = (p: best, 1-p: worst). +**Decydent (decision maker)** — osoba lub podmiot, który musi wybrać jedną z dostępnych alternatyw. Metody interaktywne wymagają dialogu z decydentem — pytamy go o postawę wobec ryzyka (optymista? pesymista?) i ew. parametry (α Hurwicza). - L = (0.6: 100 zł, 0.4: 0 zł) - E[L] = 0.6 × 100 + 0.4 × 0 = 60 zł +**Stan natury (state of nature)** — scenariusz/sytuacja zewnętrzna, na którą decydent NIE ma wpływu. Np. pogoda, koniunktura gospodarcza, zachowanie konkurencji. Oznaczamy S₁, S₂, …, Sₙ. -**Metoda loterii (lottery method)** — technika wyznaczania U(x) przez zadawanie pytań decydentowi. Ustalamy U(worst)=0, U(best)=1 i szukamy „indifference point" — prawdopodobieństwa p*, przy którym decydent jest obojętny między pewną kwotą a loterią. + Stany natury: S₁ = „dobra koniunktura", S₂ = „zła koniunktura" + Decydent NIE wybiera stanu — stan „się zdarza". - Pyt: „Wolisz 500 zł na pewno, czy loterię (p: 1000 zł, 1-p: 0 zł)?" - Jeśli punkt obojętności p* = 0.7 → U(500) = 0.7 - (Risk-neutral dałby p*=0.5, bo 500/1000=0.5) +**Macierz wypłat (payoff matrix)** — tabela, w której wiersze = alternatywy (decyzje), kolumny = stany natury, a komórki = wyniki (wypłaty). To podstawowa struktura danych dla WSZYSTKICH kryteriów decyzyjnych. -**Certainty Equivalent (CE, ekwiwalent pewności)** — pewna kwota, która jest dla decydenta równoważna danej loterii. + Przykład — macierz wypłat (zyski w tys. zł): + S₁ (dobra) S₂ (średnia) S₃ (zła) + ───────────────────────────────────────────────────── + A₁ (fabryka) 200 50 −100 + A₂ (sklep) 80 70 40 + A₃ (obligacje) 30 30 30 - Loteria: 50/50 zysk 100 zł lub 0 zł → E[X] = 50 zł - Decydent risk-averse: CE = 35 zł (wolałby 35 zł na pewno niż grać) - Risk premium = E[X] − CE = 50 − 35 = 15 zł + A₁ może dać 200k, ale też stratę 100k. + A₃ daje 30k niezależnie od stanu → decyzja bezpieczna. -**Wartość oczekiwana E[X] (expected value)** — średni wynik loterii ważony prawdopodobieństwami. +**Wartość oczekiwana E[X] (expected value)** — średni wynik ważony prawdopodobieństwami stanów natury. Używana w kryterium wartości oczekiwanej (gdy znamy prawdopodobieństwa) i w kryterium Laplace'a (z równymi prawdopodobieństwami). E[X] = Σ pᵢ × xᵢ - Dla L = (0.3: 100, 0.7: 20): E[X] = 0.3×100 + 0.7×20 = 44 + + Przykład z PRAWDZIWYMI prawdopodobieństwami (p₁=0.5, p₂=0.3, p₃=0.2): + E[A₁] = 0.5×200 + 0.3×50 + 0.2×(−100) = 100 + 15 − 20 = 95 ← MAX + E[A₂] = 0.5×80 + 0.3×70 + 0.2×40 = 40 + 21 + 8 = 69 + E[A₃] = 0.5×30 + 0.3×30 + 0.2×30 = 15 + 9 + 6 = 30 + + Dla Laplace'a (równe prawdopodobieństwa, p₁ = p₂ = p₃ = 1/3): + E[A₁] = (200 + 50 + (−100)) / 3 = 150/3 = 50 + E[A₂] = (80 + 70 + 40) / 3 = 190/3 ≈ 63.3 ← najlepsza wg Laplace'a + E[A₃] = (30 + 30 + 30) / 3 = 30 + +**Kryterium wartości oczekiwanej (expected value criterion)** — NAJPROSTSZA metoda decyzyjna W WARUNKACH RYZYKA (gdy znamy prawdopodobieństwa). Oblicz E[Aᵢ] = Σⱼ pⱼ × aᵢⱼ dla każdej alternatywy i wybierz tę z NAJWYŻSZĄ wartością oczekiwaną. + + Formuła: V(Aᵢ) = Σⱼ pⱼ × aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + Przykład (p₁=0.5, p₂=0.3, p₃=0.2): + V(A₁) = 0.5×200 + 0.3×50 + 0.2×(−100) = 95 ← MAX → wybieramy A₁ + V(A₂) = 0.5×80 + 0.3×70 + 0.2×40 = 69 + V(A₃) = 0.5×30 + 0.3×30 + 0.2×30 = 30 + + Kluczowa różnica od Laplace'a: + - Laplace: ZAKŁADA p = 1/n (bo nie znamy prawdopodobieństw) + - Wart. oczekiwana: UŻYWA PRAWDZIWYCH p (bo je znamy!) + + Przykład życiowy: firma rozważa inwestycję + - Analityk oszacował: P(boom) = 50%, P(stabilna) = 30%, P(kryzys) = 20% + - Fabryka wygrywa (E=95k), bo wysoki zysk w boomie (200k) × duże p (50%) + przeważa nad stratą w kryzysie (−100k) × małe p (20%) + + Ograniczenie: E[X] ignoruje ROZRZUT wyników! A₁ ma E=95k, ale może + dać −100k. Decydent z awersją do ryzyka może wolę A₂ (E=69k, ale + minimum 40k). Dlatego sam E[X] nie wystarczy — potrzeba też analizy + ryzyka (np. wariancji, worst-case). + + Mnemonik: „Średnia ważona — jak średnia ocen" + Wynik × prawdopodobieństwo = waga. + Sumuj wagi → E[X]. Jak w dzienniku: 5×0.3 + 4×0.5 + 2×0.2 = 3.9 + +![Kryterium wartości oczekiwanej — rozkład wyników](img/q31_expected_value.png) --- -**AHP (Analytic Hierarchy Process)** — metoda Saaty'ego do wyboru najlepszej alternatywy gdy mamy wiele kryteriów. Rozbija problem na hierarchię: Cel → Kryteria → Alternatywy. +**Kryterium decyzyjne (decision criterion)** — reguła/algorytm, który z macierzy wypłat wyznacza „najlepszą" alternatywę. Każde kryterium odzwierciedla INNĄ postawę decydenta wobec ryzyka. Dlatego to samo zadanie może dać INNE odpowiedzi zależnie od wybranego kryterium — i to jest OK. - Cel: Wybierz samochód - ├── Kryterium: Cena - │ ├── Auto A, Auto B, Auto C - ├── Kryterium: Komfort - │ ├── Auto A, Auto B, Auto C - └── Kryterium: Spalanie - ├── Auto A, Auto B, Auto C + Te same dane, różne kryteria → różne „najlepsze" decyzje: + Kryterium Wygrywa Dlaczego? + ───────────────────────────────────────────────────── + Wart. oczekiwana A₁ (95) najwyższa E[X] z prawdziwymi p + Laplace A₂ (≈63) najwyższa średnia (równe p) + Optymistyczne A₁ (200) najwyższy max + Pesymistyczne A₂ (40) najwyższy min (bezpieczne) + Hurwicz (α=0.6) A₁ (80) kompromis + Savage A₂ (120) najniższy max żalu -**Porównania parami (pairwise comparisons)** — w AHP porównujemy każdą parę kryteriów/alternatyw i oceniamy na skali 1-9 Saaty'ego: +![Porównanie kryteriów — macierz wypłat i wykresy](img/q31_criteria_comparison.png) - 1 = równe znaczenie - 3 = umiarkowana przewaga - 5 = silna przewaga - 7 = bardzo silna - 9 = absolutna przewaga +**Kryterium Laplace'a (Laplace criterion / principle of insufficient reason)** — zakładamy, że WSZYSTKIE stany natury są RÓWNIE PRAWDOPODOBNE (bo nie mamy powodu faworyzować żadnego). Obliczamy średnią arytmetyczną wypłat dla każdej alternatywy i wybieramy najwyższą. - Macierz 3×3 (Cena vs Komfort vs Spalanie): - Cena Komf Spal - Cena [ 1 3 5 ] - Komf [ 1/3 1 2 ] - Spal [ 1/5 1/2 1 ] + Formuła: V(Aᵢ) = (1/n) × Σⱼ aᵢⱼ (n = liczba stanów natury) -**Eigenvalue (wartość własna)** — z macierzy porównań wyznaczamy wektor własny → wagi kryteriów. To serce AHP: macierz parami → ranking numeryczny. + Przykład z macierzy powyżej (n=3): + V(A₁) = (200 + 50 + (−100)) / 3 = 50.0 + V(A₂) = (80 + 70 + 40) / 3 = 63.3 ← MAX → wybieramy A₂ + V(A₃) = (30 + 30 + 30) / 3 = 30.0 -**Consistency Ratio (CR)** — miara spójności ocen decydenta. Jeśli A>B i B>C, ale C>A, to niespójne. CR < 0.1 = akceptowalne. CR ≥ 0.1 → decydent powinien poprawić oceny. + Interaktywność: decydent musi zaakceptować założenie równych + prawdopodobieństw — „Czy zgadzasz się, że każdy scenariusz + jest tak samo możliwy?" + + Przykład życiowy: wybieram restaurację w nieznanym mieście. + Nie wiem, która dobra — traktuję je „po równo" i porównuję + średnią ocen z 3 portali (każdy portal = stan natury z p=1/3). + + Mnemonik: „Laplace = Loteria — Losowe, ALE Po równo" + +**Kryterium optymistyczne (maximax / optimistic criterion)** — decydent-OPTYMISTA: dla każdej alternatywy bierzemy NAJLEPSZY możliwy wynik (max w wierszu), potem wybieramy alternatywę z najwyższym z tych maksimów. + + Formuła: V(Aᵢ) = maxⱼ aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + max(A₁) = max(200, 50, −100) = 200 ← MAX → wybieramy A₁ + max(A₂) = max(80, 70, 40) = 80 + max(A₃) = max(30, 30, 30) = 30 + + A₁ wygrywa — optymista liczy na najlepszy scenariusz (200k). + Ryzyko: jeśli S₃, to strata −100k! + + Przykład życiowy: gracz w pokera, który zawsze idzie all-in, + bo „może trafię straight flush". Patrzy TYLKO na najlepsze + możliwe rozdanie. Ignoruje szansę przegranej. + + Mnemonik: „Maximax = Marzyciel — Max z Max, bo MARZĘ o najlepszym" + +**Kryterium pesymistyczne (maximin / Wald criterion)** — decydent-PESYMISTA: dla każdej alternatywy bierzemy NAJGORSZY możliwy wynik (min w wierszu), potem wybieramy alternatywę z najwyższym z tych minimów. Zabezpieczamy się przed najgorszym scenariuszem. + + Formuła: V(Aᵢ) = minⱼ aᵢⱼ → wybierz Aᵢ z max V(Aᵢ) + + min(A₁) = min(200, 50, −100) = −100 + min(A₂) = min(80, 70, 40) = 40 + min(A₃) = min(30, 30, 30) = 30 + + max{−100, 40, 30} = 40 → wybieramy A₂ + Pesymista: „Nawet w najgorszym razie dostanę 40k" (A₂ jest bezpieczna). + + Przykład życiowy: jadąc na wakacje, pesymista wybiera hotel z gwarancją + zwrotu, bo „a jeśli będzie brzydka pogoda?". Woli gwarantowany minimum + komfort niż ryzykować. Ubezpieczenia działają na tej zasadzie. + + Mnemonik: „Maximin = Mur obronny — buduję MUR pod MINimum, bo zawsze + zakładam NAJGORSZE (Wald = Wall = Mur)" + +**Kryterium Hurwicza (Hurwicz criterion)** — kompromis między optymizmem a pesymizmem. Decydent podaje współczynnik optymizmu α ∈ [0, 1], gdzie α = 1 to pełny optymista, α = 0 to pełny pesymista. + + Formuła: V(Aᵢ) = α × maxⱼ aᵢⱼ + (1−α) × minⱼ aᵢⱼ + + Dla α = 0.6: + V(A₁) = 0.6×200 + 0.4×(−100) = 120 − 40 = 80 + V(A₂) = 0.6×80 + 0.4×40 = 48 + 16 = 64 + V(A₃) = 0.6×30 + 0.4×30 = 18 + 12 = 30 + + max{80, 64, 30} = 80 → A₁ wygrywa dla α=0.6. + + Dla α = 0.3 (bardziej pesymistyczny): + V(A₁) = 0.3×200 + 0.7×(−100) = 60 − 70 = −10 + V(A₂) = 0.3×80 + 0.7×40 = 24 + 28 = 52 ← teraz A₂! + V(A₃) = 0.3×30 + 0.7×30 = 9 + 21 = 30 + + → Zmiana α zmienia wynik! Dlatego TO kryterium jest najbardziej + interaktywne — decydent MUSI podać swoje α w dialogu. + + Przypadki specjalne: + α = 1 → kryterium optymistyczne (maximax) + α = 0 → kryterium pesymistyczne (maximin) + + Przykład życiowy: kupujesz akcje. Z α=0.8 (optymista) patrzysz głównie + na potencjalny zysk. Z α=0.2 (pesymista) prawie tylko na potencjalną + stratę. α to „pokrętło optymizmu" — kręcisz i widzisz jak zmienia + się rekomendacja. + + Mnemonik: „Hurwicz = Huśtawka — huśtasz się między max a min, + α mówi jak daleko w stronę max się wychylasz" + +![Kryterium Hurwicza — wpływ α na wybór](img/q31_hurwicz_alpha.png) + +**Współczynnik optymizmu α (optimism coefficient)** — parametr Hurwicza z przedziału [0, 1]. Wyraża postawę decydenta: α bliskie 1 = optymista (wierzy w dobre scenariusze), α bliskie 0 = pesymista. + + α = 1.0 → patrzę tylko na max → maximax + α = 0.5 → równa waga max i min + α = 0.0 → patrzę tylko na min → maximin --- -**PROMETHEE (Preference Ranking Organization METHod for Enrichment Evaluations)** — metoda porównująca alternatywy parami per kryterium za pomocą funkcji preferencji. Wynik: przepływy (flows). +**Macierz żalu / macierz strat (regret matrix)** — tabela, w której każda komórka zawiera ŻALE (regret) = ile TRACĘ wybierając daną alternatywę zamiast najlepszej w danym stanie natury. - Φ⁺(a) = outgoing flow = „o ile a jest lepsze od reszty" (siła) - Φ⁻(a) = incoming flow = „o ile reszta jest lepsza od a" (słabość) - Φ(a) = Φ⁺(a) − Φ⁻(a) = net flow → im wyższe, tym lepsza alternatywa + Obliczanie: rᵢⱼ = maxₖ aₖⱼ − aᵢⱼ (max w kolumnie minus wartość w komórce) -**ELECTRE (ÉLimination Et Choix Traduisant la REalité)** — metoda outranking: A przewyższa B (A S B) gdy: + Macierz wypłat: Macierz żalu: + S₁ S₂ S₃ S₁ S₂ S₃ max żalu + A₁ 200 50 −100 A₁ 0 20 140 140 + A₂ 80 70 40 A₂ 120 0 0 120 ← MIN + A₃ 30 30 30 A₃ 170 40 10 170 -1. **Concordance (zgoda):** wystarczająco dużo kryteriów popiera A nad B -2. **Discordance (sprzeciw):** żadne kryterium nie daje B drastycznej przewagi nad A + maxₖ aₖ₁ = 200, maxₖ aₖ₂ = 70, maxₖ aₖ₃ = 40 + r₁₁ = 200−200 = 0, r₁₂ = 70−50 = 20, r₁₃ = 40−(−100) = 140 + r₂₁ = 200−80 = 120, r₂₂ = 70−70 = 0, r₂₃ = 40−40 = 0 + r₃₁ = 200−30 = 170, r₃₂ = 70−30 = 40, r₃₃ = 40−30 = 10 - Cecha AHP PROMETHEE ELECTRE - ────────────────────────────────────────────────────────── - Input parami (skala) per-kryterium per-kryterium - Wynik wagi + ranking przepływy Φ relacja outranking - Typ kompensacyjna częściowo komp. niekompensacyjna - Sens wartość globalna przepływ netto eliminacja słabych +**Kryterium Savage'a (minimax regret / Savage criterion)** — minimalizacja MAKSYMALNEGO ŻALU. Dla każdej alternatywy znajdujemy największy żal (max w wierszu macierzy żalu), potem wybieramy alternatywę z NAJMNIEJSZYM max żalem. + + Formuła: V(Aᵢ) = maxⱼ rᵢⱼ → wybierz Aᵢ z min V(Aᵢ) + + max żalu(A₁) = max(0, 20, 140) = 140 + max żalu(A₂) = max(120, 0, 0) = 120 ← MIN → wybieramy A₂ + max żalu(A₃) = max(170, 40, 10) = 170 + + Interpretacja: „Niezależnie co się zdarzy, mój żal nie przekroczy 120k" + (gdybym wybrał A₁, mógłbym żałować aż 140k; A₃ → aż 170k). + + Przykład życiowy: wybieram studia. Po 5 latach zobaczę, jaki zawód + najlepiej zarabia. Żal = „ile bym zarobił na najlepszych studiach + minus ile zarabiam". Savage minimalizuje ten maksymalny żal — + wybieram studia, po których NIGDY nie będę żałować za bardzo. + + Mnemonik: „Savage = Szał żalu — Savage to dziki (savage) żal, + więc go minimalizuję. Min z max żalu = trzymam żal na smyczy." + +![Kryterium Savage'a — budowa macierzy żalu](img/q31_regret_matrix.png) + +--- + +**Porównanie kryteriów — tabela zbiorcza:** + + Kryterium Postawa Formuła Wymaga od decydenta + ────────────────────────────────────────────────────────────────────────────── + Wart. oczekiw. racjonalna Σ pⱼ·aᵢⱼ podanie prawdopodobieństw + Laplace neutralna średnia wypłat akceptacja równych p + Optymistyczne optymista max z max nic (automatyczne) + Pesymistyczne pesymista max z min nic (automatyczne) + Hurwicza kompromis α·max + (1−α)·min podanie α ∈ [0,1] + Savage'a minimalizacja min z max żalu nic (automatyczne) + żalu + +![Mapa mnemoniczna — wszystkie kryteria](img/q31_criteria_mnemonic.png) --- @@ -183,29 +317,30 @@ Przykład ryzyka: „Z 60% szansą zysk 100 zł, z 40% strata 50 zł." Przykład ### Interaktywność = dialog z decydentem → odkrycie preferencji (funkcji użyteczności) -### Metody +### Metody (kryteria decyzyjne) -**1. Metoda loterii:** Ustal U(worst)=0, U(best)=1. Pytaj: „Wolisz x_mid na pewno, czy loterię (p: best, 1-p: worst)?" Punkt obojętności p* = U(x_mid). +**0. Kryterium wartości oczekiwanej (E[X]):** WYMAGA prawdopodobieństw stanów (warunki RYZYKA). Oblicz E[Aᵢ] = Σⱼ pⱼ·aᵢⱼ. Wybierz max. Ograniczenie: ignoruje rozrzut/ryzyko. -**2. Certainty Equivalent (CE):** CE(L) = pewna kwota równoważna loterii L. -- CE < E[X] → risk averse (wklęsła U) -- CE = E[X] → risk neutral -- CE > E[X] → risk seeking -- Risk Premium = E[X] − CE +**1. Kryterium Laplace'a:** Załóż równe prawdopodobieństwa stanów (warunki NIEPEWNOŚCI). Oblicz średnią wypłat per alternatywa. Wybierz max średniej. Formuła: V(Aᵢ) = (1/n) × Σⱼ aᵢⱼ. -**3. AHP (Analytic Hierarchy Process):** Hierarchia: Cel → Kryteria → Alternatywy. Porównania parami (skala 1-9) → eigenvalue → wagi. Consistency Ratio CR < 0.1. +**2. Kryterium optymistyczne (maximax):** Dla każdej alternatywy weź max wypłatę. Wybierz alternatywę z max z tych max. Formuła: max maxⱼ aᵢⱼ. -**4. PROMETHEE:** Funkcje preferencji per kryterium; agregacja; przepływy Φ⁺, Φ⁻, Φ (net); ranking. +**3. Kryterium pesymistyczne (maximin / Walda):** Dla każdej alternatywy weź min wypłatę. Wybierz alternatywę z max z tych min. Formuła: max minⱼ aᵢⱼ. -**5. ELECTRE:** Concordance (zgoda) + Discordance (sprzeciw) → outranking aSb. +**4. Kryterium Hurwicza:** Kompromis: V(Aᵢ) = α × maxⱼ aᵢⱼ + (1−α) × minⱼ aᵢⱼ. Decydent podaje α ∈ [0,1]. α=1 → maximax, α=0 → maximin. + +**5. Kryterium Savage'a (minimax regret):** Zbuduj macierz żalu (rᵢⱼ = maxₖ aₖⱼ − aᵢⱼ). Dla każdej alternatywy weź max żal. Wybierz alternatywę z min max żalu. ### Etymologia -**AHP** — Thomas Saaty (U. of Pittsburgh, 1970s); Analytic Hierarchy Process. **PROMETHEE** — Preference Ranking Organization METHod for Enrichment Evaluations (Jean-Pierre Brans, 1982). **ELECTRE** — ÉLimination Et Choix Traduisant la REalité (Bernard Roy, 1965) = „Eliminacja i Wybór Odzwierciedlający Rzeczywistość". **Certainty Equivalent** — z teorii użyteczności von Neumanna-Morgensterna (1944). **Funkcja użyteczności** — Daniel Bernoulli (1738) wprowadził koncepcję; vN-M sformalizowali aksjomatycznie. +**Wartość oczekiwana** — pojęcie z XVII w., Blaise Pascal i Pierre de Fermat (1654), formalizacja hazardu; „ile przeciętnie wygrasz?". **Laplace** — Pierre-Simon de Laplace (1749–1827), francuski matematyk; zasada niedostatecznej racji (principle of insufficient reason) — jeśli nie mamy powodu faworyzować żadnego stanu, traktujemy je jako równie prawdopodobne. **Wald** — Abraham Wald (1902–1950), matematyk z Wiednia; kryterium maximin = strategia minimax z teorii gier. **Hurwicz** — Leonid Hurwicz (1917–2008), laureat Nobla z ekonomii 2007 (z Myersonem i Maskinem, za mechanism design); zaproponował kompromis z parametrem α. **Savage** — Leonard Jimmie Savage (1917–1971), amerykański statystyk; kryterium minimax regret — minimalizacja żalu (1951, „The Foundations of Statistics"). ### Jak zapamiętać -- **CE = „ile dałbyś za pewniaka zamiast loterii?"** → miara awersji do ryzyka -- **AHP = „porównaj parami, policz wagi"** (macierz → eigenvalue) -- **PROMETHEE = „przepływy"** (Φ⁺ outgoing, Φ⁻ incoming) +- **E[X] = „średnia ważona prawdopodobieństwami"** → jak średnia ocen w dzienniku, ale wagi to szanse +- **Laplace = „wszystko po równo"** → średnia arytmetyczna wypłat (Loteria — ALE Po równo) +- **Maximax = „marzyciel → max z max"** → najlepszy z najlepszych, ignoruje ryzyko +- **Maximin = „mur obronny → max z min"** → najlepszy z najgorszych (Wald = Wall = Mur) +- **Hurwicz = „huśtawka — α pomiędzy"** → α·max + (1−α)·min, kręcisz pokrętłem optymizmu +- **Savage = „szał żalu → min max żalu"** → macierz żalu → minimalizuj maksymalny żal (trzymaj żal na smyczy)