feat: improved questions
1
.github/agents/obrona-expander.agent.md
vendored
@ -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)
|
- No fenced code blocks (4-space indented blocks only)
|
||||||
- Polish language throughout (English in parentheses for standard terms)
|
- Polish language throughout (English in parentheses for standard terms)
|
||||||
- Terms are ordered from foundational to advanced
|
- 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
|
### BATCH PROCESSING
|
||||||
|
|
||||||
|
|||||||
381
pytania/generate_bf_negative_diagram.py
Normal 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 C→B(−3))
|
||||||
|
|
||||||
|
A4-compatible, monochrome-friendly, 300 DPI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
DPI = 300
|
||||||
|
BG = 'white'
|
||||||
|
LN = 'black'
|
||||||
|
FS = 8
|
||||||
|
FS_TITLE = 10
|
||||||
|
FS_SMALL = 6.5
|
||||||
|
FS_EDGE = 9
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
GRAY1 = '#E8E8E8'
|
||||||
|
GRAY2 = '#D0D0D0'
|
||||||
|
GRAY3 = '#B8B8B8'
|
||||||
|
GRAY4 = '#F5F5F5'
|
||||||
|
LIGHT_GREEN = '#D5E8D4'
|
||||||
|
LIGHT_RED = '#F8D7DA'
|
||||||
|
LIGHT_YELLOW = '#FFF9C4'
|
||||||
|
|
||||||
|
# --- Graph layout for negative-weight example ---
|
||||||
|
# S→A(2), A→C(3), S→B(5), B→A(−4)
|
||||||
|
NEG_POS = {'S': (0.8, 2), 'A': (3.3, 3.2), 'B': (3.3, 0.8), 'C': (5.8, 2)}
|
||||||
|
NEG_EDGES = [('S', 'A', 2), ('A', 'C', 3), ('S', 'B', 5), ('B', 'A', -4)]
|
||||||
|
|
||||||
|
|
||||||
|
def draw_node(ax, name, pos, color='white', current=False, visited=False,
|
||||||
|
dist_label=None, fontsize=12, error=False):
|
||||||
|
x, y = pos
|
||||||
|
r = 0.35
|
||||||
|
lw = 2.5 if current else 1.5
|
||||||
|
ec = '#D32F2F' if current else ('#D32F2F' if error else LN)
|
||||||
|
fc = LIGHT_YELLOW if current else (LIGHT_GREEN if visited else color)
|
||||||
|
if error:
|
||||||
|
fc = LIGHT_RED
|
||||||
|
|
||||||
|
circle = plt.Circle((x, y), r, fill=True, facecolor=fc,
|
||||||
|
edgecolor=ec, linewidth=lw, zorder=5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(x, y, name, ha='center', va='center', fontsize=fontsize,
|
||||||
|
fontweight='bold', zorder=6)
|
||||||
|
|
||||||
|
if dist_label is not None:
|
||||||
|
bbox_ec = '#D32F2F' if error else GRAY3
|
||||||
|
bbox_fc = LIGHT_RED if error else 'white'
|
||||||
|
ax.text(x, y - 0.55, f'd={dist_label}', ha='center', va='center',
|
||||||
|
fontsize=FS, zorder=6,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=bbox_fc,
|
||||||
|
edgecolor=bbox_ec, alpha=0.95))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False,
|
||||||
|
negative=False, cycle_edge=False, offset=0.0):
|
||||||
|
x1, y1 = pos1
|
||||||
|
x2, y2 = pos2
|
||||||
|
|
||||||
|
dx, dy = x2 - x1, y2 - y1
|
||||||
|
length = np.sqrt(dx**2 + dy**2)
|
||||||
|
r = 0.38
|
||||||
|
sx = x1 + r * dx / length
|
||||||
|
sy = y1 + r * dy / length
|
||||||
|
ex = x2 - r * dx / length
|
||||||
|
ey = y2 - r * dy / length
|
||||||
|
|
||||||
|
# Offset perpendicular for parallel edges
|
||||||
|
if offset != 0:
|
||||||
|
perp_x = -dy / length * offset
|
||||||
|
perp_y = dx / length * offset
|
||||||
|
sx += perp_x; sy += perp_y
|
||||||
|
ex += perp_x; ey += perp_y
|
||||||
|
|
||||||
|
if cycle_edge:
|
||||||
|
color = '#D32F2F'
|
||||||
|
lw = 2.5
|
||||||
|
ls = '--'
|
||||||
|
elif negative:
|
||||||
|
color = '#D32F2F'
|
||||||
|
lw = 2.5
|
||||||
|
ls = '-'
|
||||||
|
elif relaxed:
|
||||||
|
color = '#D32F2F'
|
||||||
|
lw = 2.5
|
||||||
|
ls = '-'
|
||||||
|
elif highlighted:
|
||||||
|
color = '#1565C0'
|
||||||
|
lw = 2.0
|
||||||
|
ls = '-'
|
||||||
|
else:
|
||||||
|
color = GRAY3
|
||||||
|
lw = 1.5
|
||||||
|
ls = '-'
|
||||||
|
|
||||||
|
# Arrow
|
||||||
|
ax.annotate('', xy=(ex, ey), xytext=(sx, sy),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=color, lw=lw,
|
||||||
|
linestyle=ls, shrinkA=0, shrinkB=0),
|
||||||
|
zorder=2)
|
||||||
|
|
||||||
|
# Weight label
|
||||||
|
mx = (sx + ex) / 2
|
||||||
|
my = (sy + ey) / 2
|
||||||
|
perp_x = -dy / length * 0.22
|
||||||
|
perp_y = dx / length * 0.22
|
||||||
|
if offset != 0:
|
||||||
|
perp_x *= 0.5
|
||||||
|
perp_y *= 0.5
|
||||||
|
|
||||||
|
weight_str = str(weight)
|
||||||
|
edge_fc = LIGHT_RED if negative or cycle_edge else 'white'
|
||||||
|
edge_ec = '#D32F2F' if negative or cycle_edge else GRAY3
|
||||||
|
ax.text(mx + perp_x, my + perp_y, weight_str, ha='center', va='center',
|
||||||
|
fontsize=FS_EDGE, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=edge_fc,
|
||||||
|
edgecolor=edge_ec, alpha=0.95),
|
||||||
|
zorder=4)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_neg_graph(ax, edges, title="", dist=None, current=None, visited=None,
|
||||||
|
relaxed_edges=None, error_nodes=None, extra_edges=None,
|
||||||
|
node_positions=None):
|
||||||
|
if visited is None:
|
||||||
|
visited = set()
|
||||||
|
if relaxed_edges is None:
|
||||||
|
relaxed_edges = set()
|
||||||
|
if dist is None:
|
||||||
|
dist = {}
|
||||||
|
if error_nodes is None:
|
||||||
|
error_nodes = set()
|
||||||
|
if node_positions is None:
|
||||||
|
node_positions = NEG_POS
|
||||||
|
|
||||||
|
ax.set_xlim(-0.5, 7.0)
|
||||||
|
ax.set_ylim(-0.8, 4.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
if title:
|
||||||
|
ax.set_title(title, fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
all_edges = list(edges)
|
||||||
|
if extra_edges:
|
||||||
|
all_edges += extra_edges
|
||||||
|
|
||||||
|
for u, v, w in all_edges:
|
||||||
|
rl = (u, v) in relaxed_edges
|
||||||
|
neg = w < 0
|
||||||
|
cycle = extra_edges and (u, v, w) in extra_edges
|
||||||
|
# If B→A and A→B both exist, offset them
|
||||||
|
off = 0.0
|
||||||
|
draw_edge(ax, node_positions[u], node_positions[v], w,
|
||||||
|
relaxed=rl, negative=neg, cycle_edge=cycle, offset=off)
|
||||||
|
|
||||||
|
for name, pos in node_positions.items():
|
||||||
|
is_current = (name == current)
|
||||||
|
is_visited = (name in visited)
|
||||||
|
d_label = dist.get(name, None)
|
||||||
|
is_error = (name in error_nodes)
|
||||||
|
draw_node(ax, name, pos, current=is_current, visited=is_visited,
|
||||||
|
dist_label=d_label, error=is_error)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_bf_negative_weights():
|
||||||
|
"""
|
||||||
|
Two-row figure:
|
||||||
|
Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT
|
||||||
|
Row 2: B-F iterations 1-3 step by step
|
||||||
|
"""
|
||||||
|
fig = plt.figure(figsize=(14, 10))
|
||||||
|
fig.suptitle('Bellman-Ford — ujemne wagi vs Dijkstra\n'
|
||||||
|
'Graf: S→A(2), A→C(3), S→B(5), B→A(−4). Start = S',
|
||||||
|
fontsize=FS_TITLE + 1, fontweight='bold', y=0.99)
|
||||||
|
|
||||||
|
# ---- Row 1: Graph + Dijkstra wrong + BF correct ----
|
||||||
|
|
||||||
|
# Panel 1: The graph structure
|
||||||
|
ax1 = fig.add_subplot(2, 3, 1)
|
||||||
|
draw_neg_graph(ax1, NEG_EDGES,
|
||||||
|
title='Graf z ujemną wagą\n(B→A = −4, zaznaczona na czerwono)',
|
||||||
|
dist={'S': '0', 'A': '?', 'B': '?', 'C': '?'})
|
||||||
|
# START label
|
||||||
|
ax1.annotate("START", xy=(NEG_POS['S'][0] - 0.35, NEG_POS['S'][1]),
|
||||||
|
xytext=(NEG_POS['S'][0] - 1.2, NEG_POS['S'][1]),
|
||||||
|
fontsize=FS, fontweight='bold', color='#D32F2F',
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#D32F2F', lw=2),
|
||||||
|
va='center')
|
||||||
|
|
||||||
|
# Panel 2: Dijkstra — WRONG
|
||||||
|
ax2 = fig.add_subplot(2, 3, 2)
|
||||||
|
draw_neg_graph(ax2, NEG_EDGES,
|
||||||
|
title='Dijkstra — BŁĘDNY wynik\nA zamknięty z d=2, nie poprawia przy B→A',
|
||||||
|
dist={'S': '0', 'A': '2', 'B': '5', 'C': '5'},
|
||||||
|
visited={'S', 'A', 'B', 'C'},
|
||||||
|
error_nodes={'A', 'C'})
|
||||||
|
# Add "WRONG" annotations
|
||||||
|
ax2.text(NEG_POS['A'][0] + 0.6, NEG_POS['A'][1] + 0.3, '✗ powinno 1',
|
||||||
|
fontsize=FS_SMALL, color='#D32F2F', fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_RED,
|
||||||
|
edgecolor='#D32F2F', alpha=0.9, lw=0.5))
|
||||||
|
ax2.text(NEG_POS['C'][0] + 0.05, NEG_POS['C'][1] + 0.55, '✗ powinno 4',
|
||||||
|
fontsize=FS_SMALL, color='#D32F2F', fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_RED,
|
||||||
|
edgecolor='#D32F2F', alpha=0.9, lw=0.5))
|
||||||
|
|
||||||
|
# Panel 3: Bellman-Ford — CORRECT
|
||||||
|
ax3 = fig.add_subplot(2, 3, 3)
|
||||||
|
draw_neg_graph(ax3, NEG_EDGES,
|
||||||
|
title='Bellman-Ford — POPRAWNY wynik\nUjemna waga B→A poprawnie propagowana',
|
||||||
|
dist={'S': '0', 'A': '1', 'B': '5', 'C': '4'},
|
||||||
|
visited={'S', 'A', 'B', 'C'},
|
||||||
|
relaxed_edges={('B', 'A')})
|
||||||
|
ax3.text(NEG_POS['A'][0] + 0.6, NEG_POS['A'][1] + 0.3, '✓ poprawne!',
|
||||||
|
fontsize=FS_SMALL, color='#006400', fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor='#006400', alpha=0.9, lw=0.5))
|
||||||
|
ax3.text(NEG_POS['C'][0] + 0.05, NEG_POS['C'][1] + 0.55, '✓ poprawne!',
|
||||||
|
fontsize=FS_SMALL, color='#006400', fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor='#006400', alpha=0.9, lw=0.5))
|
||||||
|
|
||||||
|
# ---- Row 2: B-F iterations step by step ----
|
||||||
|
iterations = [
|
||||||
|
{
|
||||||
|
'title': 'B-F Iteracja 1\nRelaksuj WSZYSTKIE krawędzie',
|
||||||
|
'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '5'},
|
||||||
|
'relaxed': {('S', 'A'), ('A', 'C'), ('S', 'B'), ('B', 'A')},
|
||||||
|
'detail': ('S→A: 0+2=2<∞ → A=2\n'
|
||||||
|
'A→C: 2+3=5<∞ → C=5\n'
|
||||||
|
'S→B: 0+5=5<∞ → B=5\n'
|
||||||
|
'B→A: 5−4=1<2 → A=1 ✓'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'B-F Iteracja 2\nPropagacja poprawionego A',
|
||||||
|
'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '4'},
|
||||||
|
'relaxed': {('A', 'C')},
|
||||||
|
'detail': ('S→A: 0+2=2>1 ✗\n'
|
||||||
|
'A→C: 1+3=4<5 → C=4 ✓\n'
|
||||||
|
'S→B: 0+5=5=5 ✗\n'
|
||||||
|
'B→A: 5−4=1=1 ✗'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'B-F Iteracja 3\nBrak zmian → stabilne!',
|
||||||
|
'dist': {'S': '0', 'A': '1', 'B': '5', 'C': '4'},
|
||||||
|
'relaxed': set(),
|
||||||
|
'detail': ('Wszystkie krawędzie:\n'
|
||||||
|
'brak poprawy ✗\n'
|
||||||
|
'→ wynik stabilny\n'
|
||||||
|
'→ BRAK cyklu ujemnego'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, it in enumerate(iterations):
|
||||||
|
ax = fig.add_subplot(2, 3, i + 4)
|
||||||
|
draw_neg_graph(ax, NEG_EDGES, title=it['title'],
|
||||||
|
dist=it['dist'],
|
||||||
|
visited={'S', 'A', 'B', 'C'},
|
||||||
|
relaxed_edges=it['relaxed'])
|
||||||
|
# Detail text below graph
|
||||||
|
ax.text(3.2, -0.5, it['detail'], ha='center', va='top',
|
||||||
|
fontsize=FS_SMALL, family='monospace',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# Bottom note
|
||||||
|
fig.text(0.5, 0.01,
|
||||||
|
'Dijkstra zamyka wierzchołki na stałe (zachłanność) → ujemna waga B→A(−4) nie może poprawić zamkniętego A.\n'
|
||||||
|
'Bellman-Ford relaksuje WSZYSTKIE krawędzie w każdej iteracji → ujemne wagi propagują się poprawnie.',
|
||||||
|
ha='center', fontsize=FS, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW, edgecolor=LN))
|
||||||
|
|
||||||
|
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_negative_weights.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ bellman_ford_negative_weights.png")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_bf_negative_cycle():
|
||||||
|
"""
|
||||||
|
Figure showing negative cycle detection.
|
||||||
|
Graph: S→A(2), A→C(3), S→B(5), B→A(−4), C→B(−3) [added edge]
|
||||||
|
Cycle: B→A→C→B = −4+3+(−3) = −4 < 0
|
||||||
|
"""
|
||||||
|
cycle_edges = NEG_EDGES + [('C', 'B', -3)]
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=(14, 5.5))
|
||||||
|
fig.suptitle('Bellman-Ford — wykrywanie cyklu ujemnego\n'
|
||||||
|
'Dodano krawędź C→B(−3). Cykl: B→A→C→B = −4+3+(−3) = −4 < 0',
|
||||||
|
fontsize=FS_TITLE + 1, fontweight='bold', y=0.99)
|
||||||
|
|
||||||
|
# Panel 1: Graph with cycle highlighted
|
||||||
|
ax1 = fig.add_subplot(1, 3, 1)
|
||||||
|
draw_neg_graph(ax1, NEG_EDGES,
|
||||||
|
title='Graf z cyklem ujemnym\nDodana krawędź C→B(−3) — przerywana',
|
||||||
|
dist={'S': '0', 'A': '?', 'B': '?', 'C': '?'},
|
||||||
|
extra_edges=[('C', 'B', -3)])
|
||||||
|
# Mark cycle
|
||||||
|
ax1.annotate("CYKL\n−4+3+(−3)=−4<0",
|
||||||
|
xy=(3.3, 2.0),
|
||||||
|
fontsize=FS, fontweight='bold', color='#D32F2F',
|
||||||
|
ha='center', va='center',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_RED,
|
||||||
|
edgecolor='#D32F2F', alpha=0.9))
|
||||||
|
|
||||||
|
# Panel 2: After V−1 iterations — still changing
|
||||||
|
ax2 = fig.add_subplot(1, 3, 2)
|
||||||
|
draw_neg_graph(ax2, NEG_EDGES,
|
||||||
|
title='Po V−1=3 iteracjach\ndist wciąż maleje (niestabilne!)',
|
||||||
|
dist={'S': '0', 'A': '−7', 'B': '−4', 'C': '−4'},
|
||||||
|
visited={'S', 'A', 'B', 'C'},
|
||||||
|
error_nodes={'A', 'B', 'C'},
|
||||||
|
extra_edges=[('C', 'B', -3)])
|
||||||
|
ax2.text(3.2, -0.4,
|
||||||
|
'Każde okrążenie cyklu\nzmniejsza dist o 4.\n'
|
||||||
|
'Dist → −∞ (brak minimum!)',
|
||||||
|
ha='center', va='top', fontsize=FS_SMALL, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_RED,
|
||||||
|
edgecolor='#D32F2F'))
|
||||||
|
|
||||||
|
# Panel 3: V-th iteration detects
|
||||||
|
ax3 = fig.add_subplot(1, 3, 3)
|
||||||
|
ax3.axis('off')
|
||||||
|
ax3.set_xlim(0, 10)
|
||||||
|
ax3.set_ylim(0, 10)
|
||||||
|
|
||||||
|
detection_text = (
|
||||||
|
"V-ta iteracja (sprawdzenie):\n"
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
"for (src, dst, w) in edges:\n"
|
||||||
|
" if dist[src]+w < dist[dst]:\n"
|
||||||
|
" return None # CYKL!\n\n"
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
"Sprawdzamy np. krawędź B→A:\n"
|
||||||
|
" dist[B] + (−4) = −4 + (−4) = −8\n"
|
||||||
|
" −8 < dist[A] = −7\n"
|
||||||
|
" → NADAL SIĘ POPRAWIA!\n"
|
||||||
|
" → CYKL UJEMNY WYKRYTY!\n\n"
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
"Wynik: return None\n"
|
||||||
|
"(najkrótsza ścieżka nie istnieje)"
|
||||||
|
)
|
||||||
|
ax3.text(5, 5, detection_text, ha='center', va='center',
|
||||||
|
fontsize=FS + 0.5, family='monospace',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.6', facecolor=LIGHT_RED,
|
||||||
|
edgecolor='#D32F2F', lw=2))
|
||||||
|
ax3.set_title('Wykrywanie — V-ta iteracja\nJeśli cokolwiek się poprawia → cykl ujemny!',
|
||||||
|
fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
# Bottom note
|
||||||
|
fig.text(0.5, 0.01,
|
||||||
|
'Bez cyklu ujemnego: po V−1 iteracjach dist jest stabilne. '
|
||||||
|
'Z cyklem ujemnym: dist maleje w nieskończoność → V-ta iteracja to wykrywa.',
|
||||||
|
ha='center', fontsize=FS, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW, edgecolor=LN))
|
||||||
|
|
||||||
|
plt.tight_layout(rect=[0, 0.06, 1, 0.94])
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_negative_cycle.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ bellman_ford_negative_cycle.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating Bellman-Ford negative weight diagrams...")
|
||||||
|
generate_bf_negative_weights()
|
||||||
|
generate_bf_negative_cycle()
|
||||||
|
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")
|
||||||
@ -286,6 +286,220 @@ def generate_three_pillars():
|
|||||||
print(f" Saved: {out}")
|
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
|
# Main
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@ -294,4 +508,6 @@ if __name__ == '__main__':
|
|||||||
generate_pattern_template()
|
generate_pattern_template()
|
||||||
generate_catalog_map()
|
generate_catalog_map()
|
||||||
generate_three_pillars()
|
generate_three_pillars()
|
||||||
|
generate_observer_card_filled()
|
||||||
|
generate_pattern_language_navigation()
|
||||||
print("Done!")
|
print("Done!")
|
||||||
|
|||||||
1637
pytania/generate_q23_diagrams.py
Normal file
1401
pytania/generate_q24_diagrams.py
Normal file
620
pytania/generate_q31_diagrams.py
Normal 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)
|
||||||
BIN
pytania/img/bellman_ford_negative_cycle.png
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
pytania/img/bellman_ford_negative_weights.png
Normal file
|
After Width: | Height: | Size: 583 KiB |
BIN
pytania/img/q14_observer_card_filled.png
Normal file
|
After Width: | Height: | Size: 351 KiB |
BIN
pytania/img/q14_pattern_language_navigation.png
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
pytania/img/q23_diy_thresholding.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
pytania/img/q23_diy_unet.png
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
pytania/img/q23_dot_product.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
pytania/img/q23_fc_vs_conv1x1.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
pytania/img/q23_mean_shift.png
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
pytania/img/q23_mnemonics.png
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
pytania/img/q23_normalized_cuts.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
pytania/img/q23_otsu_bimodal.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
pytania/img/q23_receptive_field.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
pytania/img/q23_region_growing.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
pytania/img/q23_relu.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
pytania/img/q23_transformer_attention.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
pytania/img/q23_unet_arch.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
pytania/img/q23_watershed.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
pytania/img/q24_anchor_boxes.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
pytania/img/q24_cnn_architecture.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
pytania/img/q24_detection_tasks.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
pytania/img/q24_detector_from_classifier.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
pytania/img/q24_detr_pipeline.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
pytania/img/q24_fpn.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
pytania/img/q24_haar_features.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
pytania/img/q24_hog_gradient_steps.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
pytania/img/q24_hog_svm_pipeline.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
pytania/img/q24_integral_image.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
pytania/img/q24_iou_diagram.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
pytania/img/q24_nms_steps.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
pytania/img/q24_rcnn_evolution.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
pytania/img/q24_roi_pooling.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
pytania/img/q24_sliding_window.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
pytania/img/q24_svm_hyperplane.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
pytania/img/q24_two_vs_one_stage.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
pytania/img/q24_viola_jones_cascade.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
pytania/img/q24_yolo_grid.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
pytania/img/q31_conditions_spectrum.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
pytania/img/q31_criteria_comparison.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
pytania/img/q31_criteria_mnemonic.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
pytania/img/q31_expected_value.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
pytania/img/q31_hurwicz_alpha.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
pytania/img/q31_regret_matrix.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
@ -193,6 +193,10 @@ Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny):
|
|||||||
Po V−1 iteracjach dist nadal maleje → V-ta iteracja:
|
Po V−1 iteracjach dist nadal maleje → V-ta iteracja:
|
||||||
dist[src] + weight < dist[dst] → return None
|
dist[src] + weight < dist[dst] → return None
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu):
|
**A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu):
|
||||||
|
|||||||
@ -85,31 +85,75 @@
|
|||||||
|
|
||||||
### Katalogowanie — trzy filary metodologii
|
### Katalogowanie — trzy filary metodologii
|
||||||
|
|
||||||
**1. Ustandaryzowany szablon opisu** — każdy wzorzec opisany wg tego samego formatu:
|
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ę.
|
||||||
- **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
|
|
||||||
|
|
||||||
**2. Klasyfikacja wieloosiowa** — wzorce organizowane wzdłuż kilku osi jednocześnie:
|

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

|
||||||
|
|
||||||
|
**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)
|
- **Skala**: architektoniczny (cały system) → projektowy (klasa) → idiomatyczny (linia kodu)
|
||||||
- **Domena problemu**: kreacyjne / strukturalne / behawioralne (GoF) albo warstwy / komunikacja / dekompozycja (POSA)
|
- **Domena problemu**: kreacyjne / strukturalne / behawioralne (GoF) albo warstwy / komunikacja / dekompozycja (POSA)
|
||||||
- **Atrybut jakościowy**: wydajność, skalowalność, testowalność, dostępność
|
- **Atrybut jakościowy**: wydajność, skalowalność, testowalność, dostępność
|
||||||
|
|
||||||
**3. Język wzorców (pattern language)** — wzorce referują się wzajemnie, tworząc graf:
|
Konkretny przykład — jak GoF klasyfikuje 23 wzorce na dwóch osiach:
|
||||||
- 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"
|
|
||||||
|
|
||||||
**Konkretne katalogi:**
|
| | Kreacyjne (5) | Strukturalne (7) | Behawioralne (11) |
|
||||||
- **POSA** (1996) — wzorce architektoniczne: Layers, Pipes & Filters, Broker, MVC, Microkernel
|
|---|---|---|---|
|
||||||
- **GoF** (1994) — 23 wzorce projektowe: kreacyjne (5), strukturalne (7), behawioralne (11)
|
| **Klasa** | Factory Method | Adapter (class) | Interpreter, Template Method |
|
||||||
- **EIP** (2003) — wzorce integracji: Message Channel, Router, Aggregator
|
| **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 |
|
||||||
- **PoEAA** (2002) — enterprise: Repository, Unit of Work, Domain Model, Active Record
|
|
||||||
- **Cloud Patterns** (~2015) — chmurowe: Circuit Breaker, Sidecar, Saga, Strangler Fig
|
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).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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
|
### Przykładowe wzorce
|
||||||
|
|
||||||
@ -136,16 +180,43 @@
|
|||||||
|
|
||||||
### Jak zapamiętać
|
### Jak zapamiętać
|
||||||
|
|
||||||
- **Mnemonik katalogów „PGEP+C"**: **P**OSA → **G**oF → **E**IP → **P**oEAA + **C**loud
|
**Mnemonik 1 — szablon wzorca „NaPSiRoKo":**
|
||||||
- Historia: „**P**aweł **G**rał **E**fektownie **P**od **C**hmurami"
|
- **Na**zwa → **P**roblem → **Si**ły → **Ro**związanie → **Ko**nsekwencje
|
||||||
- Chronologicznie: GoF '94 → POSA '96 → PoEAA '02 → EIP '03 → Cloud ~'15
|
- 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"
|
||||||
- **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)
|
- 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)
|
**Mnemonik 2 — katalogi „PGEP+C" = „Paweł Grał Efektownie Pod Chmurami":**
|
||||||
- **„Monolith first"** — rozdzielaj gdy znasz granice domen
|
|
||||||
- **Wzorzec = Nazwa + Problem + Rozwiązanie + Konsekwencje** (minimum do zapamiętania z dowolnego katalogu)
|
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
|
- 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
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,261 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Robot przemysłowy (industrial robot)** — manipulator: ramię mechaniczne z 4–7 osiami obrotu (degrees of freedom), zamocowane na stałe, sterowane komputerowo. Typowe zastosowania: spawanie, malowanie, paletyzacja, montaż. Standard ISO 8373: „automatycznie sterowany, reprogramowalny, wielozadaniowy manipulator". Przykłady: ABB IRB 6700 (spawanie karoserii), KUKA KR 210 (paletyzacja), FANUC M-20iA (montaż elektroniki). W odróżnieniu od cobotów (collaborative robots), roboty przemysłowe pracują w klatkach bezpieczeństwa — nie wolno wchodzić w ich zasięg podczas pracy.
|
||||||
|
|
||||||
|
Robot przemysłowy — budowa:
|
||||||
|
[Podstawa] → [Oś 1: obrót] → [Oś 2: ramię] → [Oś 3: łokieć]
|
||||||
|
→ [Oś 4: nadgarstek] → [Oś 5: pochylenie] → [Oś 6: obrót TCP]
|
||||||
|
↓
|
||||||
|
[Narzędzie/chwytak]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Język ogólnego przeznaczenia (general-purpose language)** — język programowania zaprojektowany do rozwiązywania DOWOLNYCH problemów: aplikacje webowe, bazy danych, gry, systemy operacyjne, AI. Przykłady: C++, Python, Java, C#, Rust. Nie ma wbudowanych komend ruchu robota — trzeba pisać biblioteki lub sterowniki od zera. W kontekście robotyki: C++ i Python używane z frameworkami (ROS, MoveIt), ale NIE są to języki specjalizowane.
|
||||||
|
|
||||||
|
// C++ (ogólny) — żeby ruszyć robotem, musisz:
|
||||||
|
robot.setJointAngle(0, 45.0); // sam zarządzasz komunikacją
|
||||||
|
robot.setJointAngle(1, 30.0); // sam pilnujesz kinematyki
|
||||||
|
robot.sendCommand(); // sam wysyłasz pakiety
|
||||||
|
|
||||||
|
// RAPID (specjalizowany) — komenda ruchu to prymityw języka:
|
||||||
|
MoveL pTarget, v500, fine, tool1; // gotowe, 1 linia
|
||||||
|
|
||||||
|
**Składnia (syntax)** — reguły, JAK pisać poprawne polecenia w danym języku. Składnia określa: jakie słowa kluczowe istnieją, jak je łączyć, jak kończyć instrukcje, jak grupować bloki kodu. Analogia: składnia to gramatyka języka naturalnego — „Ala ma kota" jest poprawne, „kota Ala ma" jest zrozumiałe, ale „ma Ala kota" w programowaniu dałoby błąd.
|
||||||
|
|
||||||
|
Różne składnie — ta sama instrukcja „jeśli x > 5, zrób coś":
|
||||||
|
C-like: if (x > 5) { doSomething(); }
|
||||||
|
Pascal-like: IF x > 5 THEN doSomething; ENDIF
|
||||||
|
Python-like: if x > 5:\n do_something()
|
||||||
|
|
||||||
|
**Typy danych (data types)** — kategorie wartości, jakie język rozpoznaje. Typ mówi kompilatorowi/interpreterowi ile pamięci zająć i jakie operacje są dozwolone. Języki robotów mają UNIKALNE typy, których nie znajdziesz w C++ czy Pythonie.
|
||||||
|
|
||||||
|
Typy ogólne (istnieją w każdym języku):
|
||||||
|
INT / num → liczba całkowita: 42, -7, 0
|
||||||
|
REAL / float → liczba zmiennoprzecinkowa: 3.14, -0.001
|
||||||
|
BOOL → prawda/fałsz: TRUE, FALSE
|
||||||
|
STRING → tekst: "Hello"
|
||||||
|
|
||||||
|
Typy SPECYFICZNE dla robotyki (nie istnieją w C++/Python):
|
||||||
|
robtarget → pozycja + orientacja + konfiguracja ramienia (RAPID)
|
||||||
|
E6POS → x,y,z + a,b,c (kąty Eulera) + osie zewnętrzne (KRL)
|
||||||
|
POSITION → pozycja kartezjańska (Karel)
|
||||||
|
pose → [x, y, z, rx, ry, rz] (URScript)
|
||||||
|
tooldata → definicja narzędzia (TCP + masa + środek ciężkości)
|
||||||
|
speeddata → prędkość TCP + prędkość obrotowa
|
||||||
|
zonedata → strefa zbliżenia (jak blisko celu jechać)
|
||||||
|
|
||||||
|
**Instrukcja (instruction/statement)** — pojedyncze polecenie w programie. Komputer/robot wykonuje instrukcje po kolei (sekwencyjnie). W językach robotów instrukcje dzielą się na:
|
||||||
|
|
||||||
|
1. Ruchu: MoveL pTarget, v500, fine, tool1;
|
||||||
|
2. I/O: SetDO doGripper, 1;
|
||||||
|
3. Czekania: WaitTime 0.5;
|
||||||
|
4. Kontroli: IF sensor = TRUE THEN ...
|
||||||
|
5. Przypisania: nCycles := nCycles + 1;
|
||||||
|
6. Wywołania: PickPart; (wywołanie procedury)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Zadanie robotyczne (robotic task)** — czynność, którą robot ma wykonać w ramach procesu produkcyjnego. Składa się z sekwencji ruchów, operacji I/O i logiki. Przykłady:
|
||||||
|
- Pick & place: podnieś obiekt z punktu A, przenieś do punktu B
|
||||||
|
- Spawanie: jedź po ścieżce spoiny z włączonym łukiem
|
||||||
|
- Paletyzacja: układaj pudła na palecie w warstwy 4×3
|
||||||
|
- Montaż: włóż kołek w otwór z kontrolą siły
|
||||||
|
|
||||||
|
**Ruch robota (robot motion)** — zmiana pozycji ramienia robota w czasie. Każdy ruch jest zdefiniowany przez cztery elementy: CEL (dokąd), TYP (jak — liniowo, stawowo, po łuku), PRĘDKOŚĆ (jak szybko), PRECYZJA (strefa zbliżenia). W językach robotów ruch to PRYMITYW — jedno polecenie, nie pętla obliczeń.
|
||||||
|
|
||||||
|
Trzy typy ruchu — jak robot jedzie z A do B:
|
||||||
|
|
||||||
|
PTP / MoveJ (joint):
|
||||||
|
A ●─ ─ ─ ── ● B ← ścieżka TCP nieprzewidywalna (krzywa)
|
||||||
|
Ale najszybszy! Interpolacja w przestrzeni stawów.
|
||||||
|
|
||||||
|
LIN / MoveL (linear):
|
||||||
|
A ●──────────● B ← TCP jedzie po prostej linii
|
||||||
|
Wolniejszy, ale precyzyjna ścieżka. Wymaga ciągłego IK.
|
||||||
|
|
||||||
|
CIRC / MoveC (circular):
|
||||||
|
A ●╲ ╱● B ← TCP jedzie po łuku kołowym
|
||||||
|
╲ ● H ╱ H = punkt pomocniczy (definiuje łuk)
|
||||||
|
╲─────╱
|
||||||
|
|
||||||
|
**Definiowanie ruchów** — w językach specjalizowanych ruch definiujesz JEDNĄ instrukcją z parametrami. Nie musisz pisać pętli sterowania silnikami — język ukrywa kinematykę odwrotną, planowanie trajektorii i sterowanie serwomechanizmami.
|
||||||
|
|
||||||
|
MoveL pTarget, v500, z10, tGripper;
|
||||||
|
│ │ │ │ └── narzędzie (jaki TCP)
|
||||||
|
│ │ │ └── precyzja (strefa zbliżenia)
|
||||||
|
│ │ └── prędkość (500 mm/s)
|
||||||
|
│ └── cel (pozycja docelowa)
|
||||||
|
└── typ ruchu (liniowy)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**I/O cyfrowe i analogowe (digital/analog I/O)** — interfejsy wejścia/wyjścia robota do komunikacji z urządzeniami zewnętrznymi (chwytaki, czujniki, przenośniki, lampy sygnalizacyjne).
|
||||||
|
|
||||||
|
- **I/O cyfrowe (digital)** — sygnał ma dwa stany: 0 (OFF) lub 1 (ON). Jak wyłącznik światła. Użycie: włącz/wyłącz chwytak, sprawdź czy czujnik wykrył obiekt, sygnalizuj gotowość do PLC.
|
||||||
|
|
||||||
|
- **I/O analogowe (analog)** — sygnał ciągły w zakresie, np. 0–10V lub 4–20mA. Jak regulator głośności. Użycie: ustaw siłę chwytaka proporcjonalnie (nie tylko ON/OFF), odczytaj temperaturę z termopary, ustaw prędkość przenośnika.
|
||||||
|
|
||||||
|
I/O cyfrowe — jak włącznik (ON/OFF):
|
||||||
|
SetDO doGripper, 1; // RAPID: włącz chwytak (ON)
|
||||||
|
SetDO doGripper, 0; // RAPID: wyłącz chwytak (OFF)
|
||||||
|
OUT 1 TRUE // KRL: włącz wyjście 1
|
||||||
|
DOUT[1] = ON // Karel: włącz wyjście 1
|
||||||
|
set_digital_out(0, True) // URScript
|
||||||
|
|
||||||
|
I/O analogowe — jak potencjometr (wartość ciągła):
|
||||||
|
SetAO aoForce, 5.7; // RAPID: ustaw wyjście analogowe na 5.7V
|
||||||
|
// np. siła chwytaka proporcjonalna do napięcia:
|
||||||
|
// 0V = brak siły, 10V = maksymalna siła
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Pozycja (position)** — punkt w przestrzeni, do którego robot ma dojechać. Opisywana trzema współrzędnymi: x, y, z (odległości w milimetrach od początku układu współrzędnych). Sama pozycja to za mało — robot musi też wiedzieć, JAK narzędzie ma być obrócone w tym punkcie (orientacja).
|
||||||
|
|
||||||
|
Pozycja: (x=400, y=200, z=100)
|
||||||
|
Znaczenie: 400mm do przodu, 200mm w prawo, 100mm w górę
|
||||||
|
od początku układu współrzędnych robota (podstawa)
|
||||||
|
|
||||||
|
**Układ kartezjański (Cartesian coordinate system)** — układ współrzędnych xyz, w którym każdy punkt w przestrzeni jest opisany trzema prostopadłymi odległościami od początku. Nazwa od René Descartesa (Kartezjusz). W robotyce: pozycja TCP wyrażona w mm: x=400, y=200, z=100. Alternatywa: przestrzeń stawowa (kąty: q₁=30°, q₂=45°, q₃=-10°…), która opisuje tę samą pozycję z perspektywy silników.
|
||||||
|
|
||||||
|
Układ kartezjański:
|
||||||
|
Z ↑
|
||||||
|
│ ● TCP (400, 200, 100)
|
||||||
|
│ ╱
|
||||||
|
│ ╱
|
||||||
|
│ ╱
|
||||||
|
├──────→ Y
|
||||||
|
╱
|
||||||
|
╱
|
||||||
|
X
|
||||||
|
|
||||||
|
Przestrzeń stawowa (ta sama pozycja, ale w kątach):
|
||||||
|
q₁ = 26.6°, q₂ = 45.0°, q₃ = -12.3°, q₄ = 0°, q₅ = 57.3°, q₆ = 0°
|
||||||
|
|
||||||
|
**Orientacja (orientation)** — kierunek, w jakim narzędzie (TCP) jest skierowane w danym punkcie. Pozycja mówi GDZIE jest, orientacja mówi JAK jest obrócone. Wyrażana jako kwaternion (q1,q2,q3,q4) w RAPID lub kąty Eulera (A,B,C) w KRL.
|
||||||
|
|
||||||
|
Ta sama pozycja, różne orientacje:
|
||||||
|
Orientacja 1: chwytak skierowany w dół ↓ (spawanie poziomej płyty)
|
||||||
|
Orientacja 2: chwytak skierowany w bok → (wkładanie elementu w otwór)
|
||||||
|
Orientacja 3: chwytak skierowany w górę ↑ (podpieranie od spodu)
|
||||||
|
|
||||||
|
W RAPID (kwaternion): [1, 0, 0, 0] = narzędzie pionowo w dół
|
||||||
|
W KRL (kąty Eulera): A=0, B=0, C=180 = obrót 180° wokół Z
|
||||||
|
|
||||||
|
**Konfiguracja ramienia (robot configuration)** — dla jednej pozycji + orientacji robot 6-osiowy może mieć do 8 różnych ustawień stawów (jak zgięcie łokcia do góry lub do dołu). Konfiguracja to dodatkowy parametr, który mówi robotowi KTÓRE rozwiązanie kinematyki odwrotnej wybrać.
|
||||||
|
|
||||||
|
Ta sama pozycja, dwie konfiguracje:
|
||||||
|
|
||||||
|
Konfiguracja 1 ("łokieć do góry"): Konfiguracja 2 ("łokieć na dół"):
|
||||||
|
╱──╲ ╲──╱
|
||||||
|
╱ ╲ ╲ ╲
|
||||||
|
───╱ ● TCP ───╱ ● TCP
|
||||||
|
|
||||||
|
W RAPID: robtarget = [[x,y,z], [orientacja], [konfiguracja], [osie_zewn]]
|
||||||
|
Konfiguracja = [cf1, cf4, cf6, cfx] — opisuje kwadranty stawów
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Język strukturalny (structured language)** — język z jasno zdefiniowanymi blokami kodu: procedury (PROC/ENDPROC), pętle (WHILE/ENDWHILE, FOR/ENDFOR), warunki (IF/ELSE/ENDIF). Przeciwieństwo: kod spaghetti z GOTO i nienazwanymi skokami. RAPID jest strukturalny — program składa się z modułów (MODULE/ENDMODULE) zawierających procedury. Łatwy do czytania i utrzymania.
|
||||||
|
|
||||||
|
Strukturalność RAPID:
|
||||||
|
MODULE MainModule ← moduł (pojemnik)
|
||||||
|
PROC PickPart() ← procedura (nazwany blok)
|
||||||
|
IF ready THEN ← warunek
|
||||||
|
MoveL ...;
|
||||||
|
ENDIF
|
||||||
|
ENDPROC ← wyraźny koniec bloku
|
||||||
|
ENDMODULE
|
||||||
|
|
||||||
|
**Język wielozadaniowy (multitasking language)** — język umożliwiający uruchamianie wielu programów (tasków) jednocześnie na jednym kontrolerze. W RAPID: jeden task steruje ruchem ramienia, drugi monitoruje czujniki bezpieczeństwa, trzeci komunikuje się z PLC. Każdy task działa „równolegle" (w rzeczywistości: szybkie przełączanie kontekstu).
|
||||||
|
|
||||||
|
RAPID — wielozadaniowość:
|
||||||
|
Task 1 (MAIN): MoveL → MoveL → MoveL... (sterowanie ruchem)
|
||||||
|
Task 2 (SAFETY): WHILE TRUE DO (ciągły monitoring)
|
||||||
|
IF DI(emergency) THEN Stop;
|
||||||
|
ENDWHILE
|
||||||
|
Task 3 (COMM): czytaj/wysyłaj dane do PLC (komunikacja)
|
||||||
|
← kontroler przełącza się między taskami co ~4ms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Język Pascal-like** — język, którego składnia przypomina język Pascal (Niklaus Wirth, 1970). Cechy: bloki zaczynają się słowem kluczowym i kończą `END` (nie nawiasami `{}`), zmienne deklarowane na początku bloku (`VAR`/`DECL`), średniki jako separatory, brak rozróżniania wielkości liter (case-insensitive). KRL i Karel mają składnię Pascal-like.
|
||||||
|
|
||||||
|
Pascal: KRL (KUKA):
|
||||||
|
PROGRAM example; DEF PickAndPlace()
|
||||||
|
VAR x: INTEGER; DECL INT x
|
||||||
|
BEGIN ; (brak BEGIN/END w KRL,
|
||||||
|
x := 10; ; ale DEF/END pełni tę rolę)
|
||||||
|
IF x > 5 THEN IF x > 5 THEN
|
||||||
|
WriteLn('tak'); ; zrób coś
|
||||||
|
END; ENDIF
|
||||||
|
END. END
|
||||||
|
|
||||||
|
**Approximacja (approximation)** — termin KUKA odpowiadający „strefie zbliżenia" w RAPID. Parametr `$APO.CDIS = 10` oznacza: robot zaczyna skręcać w stronę następnego celu, gdy jest 10mm od bieżącego. Efekt identyczny jak `z10` w RAPID — płynniejszy ruch, szybszy cykl, ale TCP nie dochodzi dokładnie do zaprogramowanego punktu.
|
||||||
|
|
||||||
|
RAPID: MoveL p1, v500, z10, tool1; // strefa = 10mm
|
||||||
|
KRL: $APO.CDIS = 10 // approximacja = 10mm
|
||||||
|
LIN XTarget C_DIS // C_DIS = zastosuj approximację
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Język Python-like** — składnia inspirowana Pythonem: brak nawiasów klamrowych `{}`, proste wywołania funkcji, brak deklaracji typów, czytelny kod przypominający pseudokod. URScript jest Python-like — ale UWAGA: wcięcia w URScript NIE definiują bloków (w odróżnieniu od Pythona). Bloki kończą się słowem `end`.
|
||||||
|
|
||||||
|
Python: URScript:
|
||||||
|
def pick(): def pick():
|
||||||
|
target = [0.4, 0.2] target = p[0.4, 0.2, 0.1, 3.14, 0, 0]
|
||||||
|
move(target) movel(target, a=0.5, v=0.3)
|
||||||
|
end ← URScript wymaga 'end' (Python nie)
|
||||||
|
|
||||||
|
**Język skryptowy (scripting language)** — język interpretowany (nie kompilowany): program czytany i wykonywany linia po linii w trakcie działania, bez osobnego kroku kompilacji. Zalety: szybkie testowanie (zmień kod → uruchom od razu), brak czekania na kompilację. Wady: wolniejszy od skompilowanego kodu (ale dla robota to nie problem — wąskim gardłem jest fizyczny ruch, nie szybkość interpretera). URScript jest skryptowy.
|
||||||
|
|
||||||
|
Kompilowany (C++, Karel): Skryptowy (URScript, Python):
|
||||||
|
1. Napisz kod 1. Napisz kod
|
||||||
|
2. Skompiluj (czekaj...) 2. Uruchom natychmiast
|
||||||
|
3. Przenieś na kontroler ← brak kroku kompilacji
|
||||||
|
4. Uruchom
|
||||||
|
|
||||||
|
**Typowanie dynamiczne (dynamic typing)** — zmiennej NIE deklarujesz typu — język sam go rozpoznaje w momencie przypisania. Zmienna może zmieniać typ w trakcie programu. Przeciwieństwo: typowanie statyczne (C++, KRL) — typ deklarujesz z góry i nie może się zmienić.
|
||||||
|
|
||||||
|
Dynamiczne (URScript/Python): Statyczne (KRL):
|
||||||
|
x = 42 ← x jest liczbą DECL INT x ← x musi być INT
|
||||||
|
x = "hello" ← teraz x tekst x = 42 ← OK
|
||||||
|
← BRAK błędu x = "hello" ← BŁĄD kompilacji!
|
||||||
|
|
||||||
|
**Niski próg wejścia (low barrier to entry)** — URScript jest łatwy do nauki, bo łączy cechy ułatwiające start:
|
||||||
|
1. Brak deklaracji typów — nie musisz znać `INT`, `REAL`, `E6POS`
|
||||||
|
2. Składnia Python-like — jeśli znasz Pythona, czytasz URScript od razu
|
||||||
|
3. Prosty model: `movel(cel, prędkość)` — intuicyjne wywołanie
|
||||||
|
4. Coboty adresowane do małych firm bez zespołu programistów
|
||||||
|
5. Darmowy symulator URSim — uczysz się bez kupowania robota
|
||||||
|
6. Polyscope (GUI) — operator może programować drag & drop
|
||||||
|
|
||||||
|
Krzywa uczenia się (orientacyjnie):
|
||||||
|
URScript: dni (Python-like, prosty)
|
||||||
|
RAPID: tygodnie (strukturalny, wiele typów danych)
|
||||||
|
KRL: tygodnie (Pascal-like, dwa pliki)
|
||||||
|
Karel/TP: dni (TP) / tygodnie (Karel pełny)
|
||||||
|
ROS+C++: miesiące (framework + język ogólny + Linux)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Język proceduralny (procedural language)** — paradygmat programowania, w którym program to sekwencja PROCEDUR (funkcji) wywoływanych po kolei. Każda procedura wykonuje konkretne zadanie. Brak obiektów i klas (to byłby język obiektowy). Wszystkie języki robotów są proceduralne — program to lista kroków: jedź tu, zamknij chwytak, jedź tam, otwórz chwytak.
|
||||||
|
|
||||||
|
Proceduralny = lista kroków: Obiektowy = obiekty i metody:
|
||||||
|
PickPart(); robot.pick(objectA);
|
||||||
|
MoveTo(placePos); objectA.moveTo(placePos);
|
||||||
|
OpenGripper(); gripper.open();
|
||||||
|
|
||||||
|
**Język C-like** — składnia inspirowana językiem C: nawiasy klamrowe `{}` lub `:=` do przypisań, średniki na końcu instrukcji, zmienne ze znakiem `$` dla zmiennych systemowych. PDL2 (Comau) jest C-like: `$DOUT[1] := TRUE` przypomina składnię C z operatorem przypisania.
|
||||||
|
|
||||||
|
C: PDL2 (Comau):
|
||||||
|
int x = 10; VAR x : INTEGER
|
||||||
|
if (x > 5) { IF x > 5 THEN
|
||||||
|
output[1] = 1; $DOUT[1] := TRUE
|
||||||
|
} ENDIF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Poziomy abstrakcji T-R-M-S:**
|
**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.
|
**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**
|
### Klasyfikacja wg poziomu abstrakcji: **T-R-M-S**
|
||||||
|
|
||||||
1. **Task-level** — „Podnieś A, połóż na B" (PDDL, Behavior Trees)
|
1. **Task-level** — „Podnieś A, połóż na B" (PDDL, Behavior Trees)
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**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).
|
**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]
|
Obraz (jasność pikseli): [50][200][180][30][220][190]
|
||||||
@ -71,16 +107,55 @@
|
|||||||
|
|
||||||
Problem: JAK wybrać T? Ręcznie → subiektywne. Rozwiązanie → Otsu.
|
Problem: JAK wybrać T? Ręcznie → subiektywne. Rozwiązanie → Otsu.
|
||||||
|
|
||||||
**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby").
|
Mnemonik: „PRÓG na bramce" — jak bramkarz, przepuszcza piksele jaśniejsze od T,
|
||||||
|
blokuje ciemniejsze.
|
||||||
|
|
||||||
|
**Otsu** — automatyczny dobór progu. Algorytm: przetestuj WSZYSTKIE progi T=0..255, dla każdego oblicz wariancję wewnątrzklasową (jak „różnorodne" są piksele w klasie 0 i klasie 1). Wybierz T minimalizujące tę wariancję = klasy jak najbardziej jednorodne. Złożoność: O(n·L) gdzie n=piksele, L=poziomy jasności (256). Ograniczenie: działa TYLKO dla 2 klas i zakłada bimodalny histogram jasności (dwa „garby"). Patrz diagram powyżej.
|
||||||
|
|
||||||
|
Pseudokod Otsu:
|
||||||
|
best_T = 0
|
||||||
|
min_var = ∞
|
||||||
|
for T in 0..255:
|
||||||
|
c0 = piksele z jasność ≤ T
|
||||||
|
c1 = piksele z jasność > T
|
||||||
|
w0 = len(c0) / len(all_pixels)
|
||||||
|
w1 = len(c1) / len(all_pixels)
|
||||||
|
var = w0 * variance(c0) + w1 * variance(c1)
|
||||||
|
if var < min_var:
|
||||||
|
min_var = var
|
||||||
|
best_T = T
|
||||||
|
return best_T
|
||||||
|
|
||||||
|
Mnemonik: „AUTO-bramkarz Otsu" — sam sprawdza 256 progów i wybiera najlepszy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Pojęcia kluczowe dla Region Growing
|
||||||
|
|
||||||
**Region Growing (rozrastanie regionu)** — zaczynasz od jednego piksela „ziarna" (seed) wybranego ręcznie lub automatycznie. Sprawdzasz sąsiadów: jeśli sąsiad jest PODOBNY (np. |jasność_sąsiada - jasność_regionu| < próg), dodaj go do regionu. Powtarzaj aż nie ma więcej podobnych sąsiadów. Następnie nowy seed → nowy region.
|
**Region Growing (rozrastanie regionu)** — zaczynasz od jednego piksela „ziarna" (seed) wybranego ręcznie lub automatycznie. Sprawdzasz sąsiadów: jeśli sąsiad jest PODOBNY (np. |jasność_sąsiada - jasność_regionu| < próg), dodaj go do regionu. Powtarzaj aż nie ma więcej podobnych sąsiadów. Następnie nowy seed → nowy region.
|
||||||
|
|
||||||
Seed piksel (100,100) ma jasność 150
|
**Dlaczego seed „ręcznie LUB automatycznie"?** — to dwa różne scenariusze użycia:
|
||||||
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
|
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Pseudokod Region Growing:
|
||||||
region = {seed}
|
region = {seed}
|
||||||
queue = [seed]
|
queue = [seed]
|
||||||
while queue not empty:
|
while queue not empty:
|
||||||
@ -90,106 +165,189 @@
|
|||||||
region.add(neighbor)
|
region.add(neighbor)
|
||||||
queue.append(neighbor)
|
queue.append(neighbor)
|
||||||
|
|
||||||
Problem: over-segmentation — drobne szumy → małe regiony
|
Mnemonik: „PLAMA atramentu" — seed to kropla atramentu na papierze,
|
||||||
|
rozlewa się na podobne (jasne) miejsca, zatrzymuje się na granicach.
|
||||||
**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!
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**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)
|
**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ń).
|
||||||
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
|
|
||||||
|
|
||||||
**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.
|

|
||||||
|
|
||||||
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:
|
Problem: MASYWNA over-segmentation — każde lokalne minimum (nawet szum!) → osobna dolina
|
||||||
[-1 0 1] [50 50 200] (-1·50 + 0·50 + 1·200 +
|
Rozwiązanie: marker-controlled watershed — użytkownik podaje markery (seedy),
|
||||||
[-1 0 1] * [50 50 200] = -1·50 + 0·50 + 1·200 +
|
zalewamy TYLKO od tych markerów
|
||||||
[-1 0 1] [50 50 200] -1·50 + 0·50 + 1·200) = 450
|
|
||||||
|
|
||||||
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]
|
**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).
|
||||||
Decoder (zwiększa): [14×14] →up→ [28×28] →up→ [56×56] →up→ [112×112] →up→ [224×224]
|
|
||||||
|
|
||||||
Dlaczego nie sklasyfikować od razu KAŻDEGO piksela osobno?
|
Okno o promieniu h = 30 wokół punktu (100, 150):
|
||||||
Bo pojedynczy piksel nie ma kontekstu — nie wiesz, czy piksel o wartości 150
|
Bierze WSZYSTKIE piksele, których cechy (jasność, x, y)
|
||||||
to fragment nieba czy samochodu. Encoder-decoder widzi KONTEKST (cały obiekt)
|
są w odległości ≤ 30 od (100, 150).
|
||||||
i jednocześnie tworzy wynik o PEŁNEJ ROZDZIELCZOŚCI.
|
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
|
**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).
|
||||||
Ze skip connections: decoder „wie" ŻE tu jest samochód AND DOKŁADNIE GDZIE jest krawędź
|
|
||||||
|
|
||||||
**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"
|
**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.
|
||||||
FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa [H×W×C]
|
|
||||||
↑ skip connections z encodera
|
|
||||||
|
|
||||||
**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).
|

|
||||||
|
|
||||||
Encoder ──skip (concat)──→ Decoder
|
Pseudokod Mean Shift:
|
||||||
↓ ──skip (concat)──→ ↑
|
for each pixel p:
|
||||||
↓ ──skip (concat)──→ ↑
|
x = p.features # np. (R, G, B, pos_x, pos_y)
|
||||||
bottleneck (najgłębsza warstwa)
|
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,
|
Mnemonik: „KULKI toczą się do dołków" — rozsyp kulki na nierównym stole,
|
||||||
decoder wraca do góry (↑) — tworząc kształt litery U.
|
każda toczy się do najbliższego zagłębienia. Ile dołków = tyle segmentów.
|
||||||
Dlaczego concat a nie dodawanie? Więcej informacji — encoder features + decoder features
|
|
||||||
→ sieć sama decyduje, które informacje wykorzystać.
|
|
||||||
|
|
||||||
**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
|
**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).
|
||||||
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!
|
|
||||||
|
|
||||||
Dlaczego to ważne? Segmentacja wymaga KONTEKSTU — żeby wiedzieć, że piksel to
|
**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).
|
||||||
„droga", musisz zobaczyć otaczające budynki i niebo. Większe receptive field = więcej kontekstu.
|
|
||||||
|
|
||||||
**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.
|

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

|
||||||
|
|
||||||
|
**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ź!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|

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

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

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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).
|
**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?
|
### 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).
|
Metody niewymagające uczenia maszynowego — oparte na ręcznie zdefiniowanych regułach (próg, podobieństwo, struktura grafu).
|
||||||
|
|
||||||
| Metoda | Idea | Wada | Złożoność |
|
| Metoda | Idea | Wada | Złożoność | Mnemonik |
|
||||||
|--------|------|------|-----------|
|
|--------|------|------|-----------|----------|
|
||||||
| **Thresholding** | piksel > T → klasa 1, else → klasa 0 | tylko 2 klasy, proste sceny | O(n) |
|
| **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) |
|
| **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) |
|
| **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) |
|
| **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²) |
|
| **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³) |
|
| **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]
|
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.
|
||||||
Otsu automatycznie dobiera próg T=128:
|
|
||||||
Wynik: [ 0 ][ 1 ][ 1 ][ 0][ 1 ][ 1 ]
|
|
||||||
Zastosowanie: oddzielenie tekstu od tła (OCR), analiza zdjęć RTG
|
|
||||||
|
|
||||||
**Przykład — Watershed:**
|

|
||||||
|
|
||||||
Obraz traktowany jako mapa topograficzna:
|
Pseudokod Otsu (Python-style):
|
||||||
Jasne piksele = szczyty, ciemne = doliny
|
best_T, min_var = 0, float('inf')
|
||||||
"Zalewamy" od minimów → woda spotyka się na graniach → GRANICE segmentów
|
for T in range(256):
|
||||||
Problem: za wiele minimów → over-segmentation → potrzeba markers
|
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.
|
**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Ę)
|
Decoder: cechy [14] → [28] → [56] → [112] → [224×224] (odtwarza MAPĘ)
|
||||||
bottleneck
|
bottleneck
|
||||||
|
|
||||||
| Sieć | Rok | Kluczowa innowacja | Use case |
|
| Sieć | Rok | Kluczowa innowacja | Use case | Mnemonik |
|
||||||
|------|-----|-------------------|----------|
|
|------|-----|-------------------|----------|----------|
|
||||||
| **FCN** | 2015 | w pełni konwolucyjna + skip connections | pierwsza end-to-end |
|
| **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 |
|
| **U-Net** | 2015 | U-shape + skip concat + data augmentation | segmentacja medyczna | „Litera U + mosty" |
|
||||||
| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose |
|
| **DeepLab v3+** | 2018 | atrous (dilated) conv + ASPP | general-purpose | „DZIURY w filtrze" |
|
||||||
| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight |
|
| **SegFormer** | 2021 | transformer encoder (self-attention) | SOTA lightweight | „WSZYSCY ze WSZYSTKIMI" |
|
||||||
| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal |
|
| **Mask2Former** | 2022 | masked attention + unified architecture | SOTA universal | „WSZYSCY ze WSZYSTKIMI" |
|
||||||
|
|
||||||
**FCN (Fully Convolutional Network):**
|
**FCN (Fully Convolutional Network):**
|
||||||
|
|
||||||
|
Mnemonik: „FC → Conv 1×1 = otwieramy bramkę dla DOWOLNEGO rozmiaru"
|
||||||
Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot"
|
Zwykły CNN: Conv → Conv → Pool → ... → FC → FC → "kot"
|
||||||
FCN: Conv → Conv → Pool → ... → Conv → Upsample → mapa pikseli
|
FCN: Conv → Conv → Pool → ... → Conv1×1 → Upsample → mapa pikseli
|
||||||
Innowacja: zamiana FC na Conv → wejście dowolnego rozmiaru
|
Innowacja: zamiana FC na Conv1×1 → wejście dowolnego rozmiaru
|
||||||
Skip connections: łączą cechy z encodera → zachowują detale przestrzenne
|
Skip connections: łączą cechy z encodera → zachowują detale przestrzenne
|
||||||
|
|
||||||
**U-Net:**
|
**U-Net:**
|
||||||
|
|
||||||
|
Mnemonik: „Litera U + mosty" — schodzisz w dół, wracasz w górę,
|
||||||
|
po drodze mosty (skip connections z concat) przenoszą detale.
|
||||||
Encoder (↓) Decoder (↑)
|
Encoder (↓) Decoder (↑)
|
||||||
[64]────skip────→[64] ← skip connections = concatenation
|
[64]────skip────→[64] ← skip connections = concatenation
|
||||||
[128]───skip───→[128] (przenosi detale z encodera do decodera)
|
[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+:**
|
**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
|
Zwykła konwolucja 3×3: [x][x][x] receptive field = 3
|
||||||
Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5, te same parametry!
|
Dilated (rate=2): [x][ ][x][ ][x] receptive field = 5, te same parametry!
|
||||||
ASPP: równolegle rate=6,12,18 → multi-scale features → łączenie
|
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):**
|
**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)
|
CNN: filtr 3×3 widzi LOKALNY kontekst (sąsiadów)
|
||||||
Transformer: self-attention widzi CAŁY obraz naraz
|
Transformer: self-attention widzi CAŁY obraz naraz
|
||||||
Cena: O(n²) pamięci (n = piksele), ale lepsze wyniki
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
### Metryki i funkcje kosztu
|
||||||
@ -334,7 +527,38 @@ Metody uczące się automatycznie rozpoznawać cechy z danych treningowych. Wszy
|
|||||||
|
|
||||||
### Jak zapamiętać
|
### Jak zapamiętać
|
||||||
|
|
||||||
- **U-Net = „U-shape + skip connections"** — encoder-decoder
|
**Super-mnemonik na kolejność algorytmów:**
|
||||||
- **DeepLab = „Atrous (dilated) convolutions + ASPP"**
|
|
||||||
- **mIoU = Intersection / Union, uśrednione per klasa**
|
„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 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
|
||||||
|
|
||||||
|
|||||||
@ -135,17 +135,7 @@
|
|||||||
|
|
||||||
**Architektura CNN — pełny przykład (AlexNet, wygrał ImageNet 2012):**
|
**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)
|
|
||||||
|
|
||||||
ROZMIARY MALEJĄ: 224 → 55 → 27 → 13 → 6 (kompresja przestrzenna)
|
ROZMIARY MALEJĄ: 224 → 55 → 27 → 13 → 6 (kompresja przestrzenna)
|
||||||
KANAŁY ROSNĄ: 3 → 96 → 256 → 384 → 256 (coraz więcej wyuczonych cech)
|
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.
|
**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!
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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ę.
|
**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
|
|
||||||
|
|
||||||
**HOG+SVM — klasyczny pipeline detekcji pieszych:**
|
**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.
|
**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ść = Σ₂ − Σ₁ − Σ₃
|
|
||||||
└────────────┘
|
|
||||||
|
|
||||||
Dlaczego działa na TWARZACH?
|
Dlaczego działa na TWARZACH?
|
||||||
- Oczy CIEMNIEJSZE niż czoło → cecha "krawędź pozioma" daje dużą wartość
|
- 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).
|
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 ✓
|
|
||||||
|
|
||||||
Zawsze 4 odczyty z tabeli → O(1)!
|
Zawsze 4 odczyty z tabeli → O(1)!
|
||||||
Czy prostokąt ma 4 piksele czy 4 MILIONY — czas TEN SAM!
|
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".
|
**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!
|
|
||||||
|
|
||||||
Mnemonik: kaskada = "SITO" — coraz drobniejsze oczka,
|
Mnemonik: kaskada = "SITO" — coraz drobniejsze oczka,
|
||||||
na początku odpada piach, na końcu zostaje ZŁOTO (twarz).
|
na początku odpada piach, na końcu zostaje ZŁOTO (twarz).
|
||||||
@ -414,7 +364,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**R-CNN family (two-stage detectors)** — dwuetapowe: najpierw generuj propozycje regionów, potem klasyfikuj każdy region. Nazwa: Region-based CNN.
|
**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)
|
3. W każdej komórce weź MAX (jak max pooling)
|
||||||
4. Wynik: tensor 7×7 — STAŁY rozmiar niezależnie od oryginalnego ROI!
|
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]
|
|
||||||
|
|
||||||
Kluczowa sztuczka Fast R-CNN:
|
Kluczowa sztuczka Fast R-CNN:
|
||||||
CNN raz na CAŁY obraz → JEDNA feature mapa → ROI Pool 2000 regionów z TEJ SAMEJ mapy
|
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?"
|
- C prawdopodobieństw klas = „jaki to obiekt?"
|
||||||
Jedno przejście przez sieć → WSZYSTKIE detekcje naraz. 45-155 fps!
|
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)
|
|
||||||
|
|
||||||
**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.
|
**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 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-free** — nowoczesne podejście (FCOS, YOLOv8): bezpośrednia predykcja środka i wymiarów, bez predefiniowanych anchorów. Prostsza architektura, mniej hyperparametrów.
|
**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.
|
**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).
|
**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₁₀₀)]
|
|
||||||
|
|
||||||
"Object queries" = 100 wyuczonych wektorów, każdy "szuka" jednego obiektu.
|
"Object queries" = 100 wyuczonych wektorów, każdy "szuka" jednego obiektu.
|
||||||
Obraz z 5 obiektami → 5 queries dopasuje się do obiektów,
|
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).
|
**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 = pole(∩) / pole(A ∪ B)
|
IoU = pole(∩) / pole(A ∪ B)
|
||||||
= pole(∩) / (pole(A) + pole(B) − pole(∩))
|
= 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:**
|
**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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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 |
|
| **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 |
|
| **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
|
**Mnemonik kroków HOG: „GÓRA KOCHA BOGATYCH NARCIARZY" → Gradienty → Orientacja → Komórki → Bloki → Normalizacja**
|
||||||
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
|
|
||||||
|
|
||||||
**Viola-Jones (2001) — 3 innowacje:**
|

|
||||||
|
|
||||||
1. Haar features: [ jasne | ciemne ] → prosta różnica intensywności
|
**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ź.
|
||||||
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
|
Przykład liczbowy:
|
||||||
Etap 2 (10 cech): odrzuca 80% reszty
|
Piksele w wierszu: [50, 50, 200]
|
||||||
...Etap 25 (200 cech): szczegółowa analiza TYLKO 0.01% okien
|
Gx = pixel[x+1] − pixel[x−1] = 200 − 50 = 150 ← silna krawędź pionowa!
|
||||||
Efekt: ~95% detections = szybkie odrzucenia → real-time!
|
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".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Krok 4 — Bloki (B jak NARCIARZY):** Grupuj komórki w bloki 2×2 (= 16×16 px). Przesuwaj blok z krokiem 1 komórki. Okno 64×128 → (8−1)×(16−1) = 7×15 = 105 bloków.
|
||||||
|
|
||||||
|
**Krok 5 — Normalizacja (N):** Dla KAŻDEGO bloku (4 komórki × 9 binów = 36 wartości) wykonaj normalizację L2 → odporność na zmiany oświetlenia. 105 bloków × 36 = **3780 cech** → wektor HOG.
|
||||||
|
|
||||||
|
Pseudokod:
|
||||||
|
def compute_hog(window_64x128):
|
||||||
|
Gx = pixel[x+1] - pixel[x-1] # gradient poziomy
|
||||||
|
Gy = pixel[y+1] - pixel[y-1] # gradient pionowy
|
||||||
|
mag = sqrt(Gx**2 + Gy**2) # siła
|
||||||
|
dir = arctan2(Gy, Gx) * 180 / pi # kierunek 0°-180°
|
||||||
|
|
||||||
|
hog = []
|
||||||
|
for block_2x2 in sliding_blocks(cells_8x8):
|
||||||
|
block_hist = []
|
||||||
|
for cell in block_2x2: # 4 komórki
|
||||||
|
hist = [0]*9 # 9 binów
|
||||||
|
for px in cell.pixels: # 64 piksele
|
||||||
|
bin = int(dir[px] / 20) # który bin?
|
||||||
|
hist[bin] += mag[px] # ważone głosowanie
|
||||||
|
block_hist += hist
|
||||||
|
block_hist = L2_normalize(block_hist) # normalizacja!
|
||||||
|
hog += block_hist
|
||||||
|
return hog # wektor 3780 cech → do SVM
|
||||||
|
|
||||||
|
**Krok 6 — SVM klasyfikuje:** Wektor 3780 cech → SVM odpowiada: „pieszy" (+1) lub „tło" (−1).
|
||||||
|
|
||||||
|

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

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

|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
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!
|
Faster R-CNN:[CNN] → [RPN generuje propozycje] → [ROI Pool] → [FC] = 0.2s!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
**One-stage detectors (jednoetapowe)** — klasyfikacja i lokalizacja w JEDNYM przejściu.
|
**One-stage detectors (jednoetapowe)** — klasyfikacja i lokalizacja w JEDNYM przejściu.
|
||||||
|
|
||||||
| Model | Rok | Szybkość | Innowacja |
|
| Model | Rok | Szybkość | Innowacja |
|
||||||
@ -763,13 +801,7 @@ Metody sprzed deep learningu — ręcznie projektowane cechy (features) + klasyc
|
|||||||
|
|
||||||
**Two-stage vs One-stage:**
|
**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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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?
|
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
|
|
||||||
|
|
||||||
**Podejście 2 — Region Proposals + Klasyfikator (szybsze):**
|
---
|
||||||
|
|
||||||
Selective Search → ~2000 regionów (zamiast milionów)
|
#### Podejście 1 — Sliding Window (najprostsze, NAJWOLNIEJSZE)
|
||||||
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 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"
|
**Mnemonik: „WYCINAJ i PYTAJ" — jak wycinanie ciasteczek: koło po kole, aż cały obraz pokryty.**
|
||||||
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!
|
|
||||||
|
|
||||||
Podsumowanie:
|

|
||||||
Sliding Window: ~milion klasyfikacji → NIEPRAKTYCZNE
|
|
||||||
Region Proposals: ~2000 klasyfikacji → wolne ale działa (R-CNN)
|
Pseudokod:
|
||||||
Fine-tune: 1 przejście sieci → szybkie i dokładne (Faster R-CNN, YOLO)
|
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 (Non-Maximum Suppression) — post-processing
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Detektor generuje WIELE nakładających się bbox dla jednego obiektu:
|
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
|
[bbox1, 0.95], [bbox2, 0.90], [bbox3, 0.85] — wszystkie na tym samym kocie
|
||||||
|
|
||||||
Algorytm NMS:
|
Pseudokod NMS:
|
||||||
1. Sortuj po confidence: [0.95, 0.90, 0.85]
|
def nms(detections, iou_threshold=0.5):
|
||||||
2. Weź najlepszą (0.95) → ZACHOWAJ
|
detections.sort(by=confidence, descending=True)
|
||||||
3. Oblicz IoU z resztą: IoU(bbox1,bbox2)=0.82, IoU(bbox1,bbox3)=0.75
|
keep = []
|
||||||
4. Usuń te z IoU > próg (0.5): usuń bbox2 i bbox3
|
while detections:
|
||||||
5. Powtórz dla następnej najlepszej
|
best = detections.pop(0) # weź najlepszą
|
||||||
Wynik: 1 bbox per obiekt
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Mnemonik NMS: „Najlepszy Ma Się dobrze" — zachowaj najlepszą, resztę wyrzuć
|
||||||
|
Mnemonik IoU: „Ile pokrycia Ustalono?" — pole(∩) / pole(A∪B)
|
||||||
|
|
||||||
### Etymologia
|
### 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
|
- **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 = 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)
|
- **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
|
- **Transfer learning = „PRZESZCZEP GŁOWY"** — nie ucz się od zera, przenieś wiedzę z ImageNet, zmień głowicę
|
||||||
- **Viola-Jones: kaskada = „SITO"** — piach odpada wcześnie, złoto (twarz) zostaje na końcu
|
- **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
|
- **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
|
- **Selective Search** — inteligentne łączenie regionów zamiast milionów okien
|
||||||
- **ROI Pooling** — dowolny rozmiar → stały rozmiar (siatkowanie + max)
|
- **ROI Pooling** — dowolny rozmiar → stały rozmiar (siatkowanie + max)
|
||||||
- **Bbox regression = „GPS korekta"** — popraw przybliżoną pozycję o Δx, Δy, Δw, Δh
|
- **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
|
- **YOLO = „You Only Look Once"** — jednoetapowy, szybki, siatka S×S
|
||||||
- **Faster R-CNN = CNN + RPN + ROI Pool** — dwuetapowy, dokładny
|
- **Faster R-CNN = CNN + RPN + ROI Pool** — dwuetapowy, dokładny
|
||||||
- **NMS = „Najlepszy Ma Się dobrze"** — zachowaj najlepszą detekcję, usuń duplikaty
|
- **NMS = „Najlepszy Ma Się dobrze"** — zachowaj najlepszą detekcję, usuń duplikaty
|
||||||
|
- **IoU = „Ile pokrycia Ustalono?"** — pole(∩) / pole(A∪B)
|
||||||
- **DETR = „Detekcja Eliminująca Trikowe Redundancje"** — bez NMS, bez anchorów, transformer
|
- **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)
|
||||||
|
|
||||||
|
|||||||
@ -61,15 +61,16 @@
|
|||||||
│ → NIE → wróć do 1│
|
│ → 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)?"
|
Wart. oczekiwana „Znasz prawdopodobieństwa stanów?" (potrzebne p)
|
||||||
CE „Ile na pewno = ta loteria?"
|
Laplace'a „Każdy stan natury równie prawdopodobny" (założenie)
|
||||||
AHP „Ile razy kryterium A ważniejsze od B?" (skala 1-9)
|
Optymistyczne „Zawsze liczysz na najlepszy scenariusz?" (postawa)
|
||||||
PROMETHEE „Jak ważne jest każde kryterium?" (wagi)
|
Pesymistyczne „Chcesz zabezpieczyć się przed najgorszym?" (postawa)
|
||||||
ELECTRE „Jaki próg zgody/sprzeciwu?"
|
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."
|
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."
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**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ł)
|
**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ₙ.
|
||||||
E[L] = 0.6 × 100 + 0.4 × 0 = 60 zł
|
|
||||||
|
|
||||||
**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ł)?"
|
**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.
|
||||||
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)
|
|
||||||
|
|
||||||
**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ł
|
A₁ może dać 200k, ale też stratę 100k.
|
||||||
Decydent risk-averse: CE = 35 zł (wolałby 35 zł na pewno niż grać)
|
A₃ daje 30k niezależnie od stanu → decyzja bezpieczna.
|
||||||
Risk premium = E[X] − CE = 50 − 35 = 15 zł
|
|
||||||
|
|
||||||
**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ᵢ
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**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
|
Te same dane, różne kryteria → różne „najlepsze" decyzje:
|
||||||
├── Kryterium: Cena
|
Kryterium Wygrywa Dlaczego?
|
||||||
│ ├── Auto A, Auto B, Auto C
|
─────────────────────────────────────────────────────
|
||||||
├── Kryterium: Komfort
|
Wart. oczekiwana A₁ (95) najwyższa E[X] z prawdziwymi p
|
||||||
│ ├── Auto A, Auto B, Auto C
|
Laplace A₂ (≈63) najwyższa średnia (równe p)
|
||||||
└── Kryterium: Spalanie
|
Optymistyczne A₁ (200) najwyższy max
|
||||||
├── Auto A, Auto B, Auto C
|
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:
|

|
||||||
|
|
||||||
1 = równe znaczenie
|
**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ą.
|
||||||
3 = umiarkowana przewaga
|
|
||||||
5 = silna przewaga
|
|
||||||
7 = bardzo silna
|
|
||||||
9 = absolutna przewaga
|
|
||||||
|
|
||||||
Macierz 3×3 (Cena vs Komfort vs Spalanie):
|
Formuła: V(Aᵢ) = (1/n) × Σⱼ aᵢⱼ (n = liczba stanów natury)
|
||||||
Cena Komf Spal
|
|
||||||
Cena [ 1 3 5 ]
|
|
||||||
Komf [ 1/3 1 2 ]
|
|
||||||
Spal [ 1/5 1/2 1 ]
|
|
||||||
|
|
||||||
**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"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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)
|
Obliczanie: rᵢⱼ = maxₖ aₖⱼ − aᵢⱼ (max w kolumnie minus wartość w komórce)
|
||||||
Φ⁻(a) = incoming flow = „o ile reszta jest lepsza od a" (słabość)
|
|
||||||
Φ(a) = Φ⁺(a) − Φ⁻(a) = net flow → im wyższe, tym lepsza alternatywa
|
|
||||||
|
|
||||||
**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
|
maxₖ aₖ₁ = 200, maxₖ aₖ₂ = 70, maxₖ aₖ₃ = 40
|
||||||
2. **Discordance (sprzeciw):** żadne kryterium nie daje B drastycznej przewagi nad A
|
r₁₁ = 200−200 = 0, r₁₂ = 70−50 = 20, r₁₃ = 40−(−100) = 140
|
||||||
|
r₂₁ = 200−80 = 120, r₂₂ = 70−70 = 0, r₂₃ = 40−40 = 0
|
||||||
|
r₃₁ = 200−30 = 170, r₃₂ = 70−30 = 40, r₃₃ = 40−30 = 10
|
||||||
|
|
||||||
Cecha AHP PROMETHEE ELECTRE
|
**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.
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
Input parami (skala) per-kryterium per-kryterium
|
Formuła: V(Aᵢ) = maxⱼ rᵢⱼ → wybierz Aᵢ z min V(Aᵢ)
|
||||||
Wynik wagi + ranking przepływy Φ relacja outranking
|
|
||||||
Typ kompensacyjna częściowo komp. niekompensacyjna
|
max żalu(A₁) = max(0, 20, 140) = 140
|
||||||
Sens wartość globalna przepływ netto eliminacja słabych
|
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."
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -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)
|
### 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.
|
**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ᵢⱼ.
|
||||||
- CE < E[X] → risk averse (wklęsła U)
|
|
||||||
- CE = E[X] → risk neutral
|
|
||||||
- CE > E[X] → risk seeking
|
|
||||||
- Risk Premium = E[X] − CE
|
|
||||||
|
|
||||||
**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
|
### Etymologia
|
||||||
|
|
||||||
**AHP** — Thomas Saaty (U. of Pittsburgh, 1970s); Analytic Hierarchy Process. **PROMETHEE** — Preference Ranking Organization METHod for Enrichment Evaluations (Jean-Pierre Brans, 1982). **ELECTRE** — ÉLimination Et Choix Traduisant la REalité (Bernard Roy, 1965) = „Eliminacja i Wybór Odzwierciedlający Rzeczywistość". **Certainty Equivalent** — z teorii użyteczności von Neumanna-Morgensterna (1944). **Funkcja użyteczności** — Daniel Bernoulli (1738) wprowadził koncepcję; vN-M sformalizowali aksjomatycznie.
|
**Wartość oczekiwana** — pojęcie z XVII w., Blaise Pascal i Pierre de Fermat (1654), formalizacja hazardu; „ile przeciętnie wygrasz?". **Laplace** — Pierre-Simon de Laplace (1749–1827), francuski matematyk; zasada niedostatecznej racji (principle of insufficient reason) — jeśli nie mamy powodu faworyzować żadnego stanu, traktujemy je jako równie prawdopodobne. **Wald** — Abraham Wald (1902–1950), matematyk z Wiednia; kryterium maximin = strategia minimax z teorii gier. **Hurwicz** — Leonid Hurwicz (1917–2008), laureat Nobla z ekonomii 2007 (z Myersonem i Maskinem, za mechanism design); zaproponował kompromis z parametrem α. **Savage** — Leonard Jimmie Savage (1917–1971), amerykański statystyk; kryterium minimax regret — minimalizacja żalu (1951, „The Foundations of Statistics").
|
||||||
|
|
||||||
### Jak zapamiętać
|
### Jak zapamiętać
|
||||||
|
|
||||||
- **CE = „ile dałbyś za pewniaka zamiast loterii?"** → miara awersji do ryzyka
|
- **E[X] = „średnia ważona prawdopodobieństwami"** → jak średnia ocen w dzienniku, ale wagi to szanse
|
||||||
- **AHP = „porównaj parami, policz wagi"** (macierz → eigenvalue)
|
- **Laplace = „wszystko po równo"** → średnia arytmetyczna wypłat (Loteria — ALE Po równo)
|
||||||
- **PROMETHEE = „przepływy"** (Φ⁺ outgoing, Φ⁻ incoming)
|
- **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)
|
||||||
|
|
||||||
|
|||||||