feat: improved questions

This commit is contained in:
Krzysztof kuhy Rudnicki 2026-02-21 19:51:31 +01:00
parent 2c970368c9
commit c86f622703
56 changed files with 6999 additions and 713 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 CB(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: 54=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: 54=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: SA(2), AC(3), SB(5), BA(4), CB(3) [added edge]
Cycle: BACB = 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\n4+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 V1 iterations — still changing
ax2 = fig.add_subplot(1, 3, 2)
draw_neg_graph(ax2, NEG_EDGES,
title='Po V1=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 V1 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}/")

View File

@ -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!")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
pytania/img/q23_relu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
pytania/img/q24_fpn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@ -193,6 +193,10 @@ Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny):
Po V1 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):

View File

@ -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

View File

@ -12,6 +12,261 @@
---
**Robot przemysłowy (industrial robot)** — manipulator: ramię mechaniczne z 47 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. 010V lub 420mA. 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)

View File

@ -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 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

View File

@ -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[x1] = 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 → (81)×(161) = 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(AB)
### 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(AB)
- **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)

View File

@ -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₁₁ = 200200 = 0, r₁₂ = 7050 = 20, r₁₃ = 40(100) = 140
r₂₁ = 20080 = 120, r₂₂ = 7070 = 0, r₂₃ = 4040 = 0
r₃₁ = 20030 = 170, r₃₂ = 7030 = 40, r₃₃ = 4030 = 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 (17491827), 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 (19021950), matematyk z Wiednia; kryterium maximin = strategia minimax z teorii gier. **Hurwicz** — Leonid Hurwicz (19172008), laureat Nobla z ekonomii 2007 (z Myersonem i Maskinem, za mechanism design); zaproponował kompromis z parametrem α. **Savage** — Leonard Jimmie Savage (19171971), 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)