Proces_uczenia_SI/laby/lab-2.ipynb

2065 lines
74 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"id": "e54b18ab-24e0-439b-b9b1-3c460d971f09",
"metadata": {},
"source": [
"# Proces uczenia się sztucznej inteligencji"
]
},
{
"cell_type": "markdown",
"id": "92ec9891-62ad-42ff-90e5-9dc7575cd8b6",
"metadata": {},
"source": [
"# Projekt"
]
},
{
"cell_type": "markdown",
"id": "da6f1c7f-7250-4d46-a76f-7ae1f9f71b3d",
"metadata": {},
"source": [
"Proszę o uzupełnienie poniższych informacji:\n",
"\n",
"Osoby w grupie (numery indeksów): _________________________________________________________\n",
"\n",
"Tytuł projektu oraz jego zakres: _________________________________________________________"
]
},
{
"cell_type": "markdown",
"id": "3d54aa2c-96f7-49f2-a9f0-6edfa76136b7",
"metadata": {},
"source": [
"## Wstęp"
]
},
{
"cell_type": "markdown",
"id": "a2ced259-6289-40df-9369-d1924a311eb2",
"metadata": {},
"source": [
"W znacznej większości projektów związanych z uczeniem maszynowym najczęstszym powodem słabych wyników **nie jest zły algorytm, tylko złe dane**. Zanim cokolwiek zaczniemy trenować, musimy poznać dane: ile ich jest, jakie mają kolumny i typy, czy zawierają braki i wartości odstające, jak wyglądają rozkłady i zależności między zmiennymi. \n",
"\n",
"Ten etap nazywa się **eksploracyjną analizą danych (EDA, Exploratory Data Analysis)**."
]
},
{
"cell_type": "markdown",
"id": "23facec4-a04b-47e6-92b9-5744a90b7fe6",
"metadata": {},
"source": [
"## Propozycja składu merytorycznego projektu"
]
},
{
"cell_type": "markdown",
"id": "4b39a9d6-53f8-444d-baf5-8f85d72b7eea",
"metadata": {},
"source": [
"Proszę śmiało wykorzystać poniższą propozycję jako ramy do projektu. Tak, wszystko powinno być zawarte w **jednym** pliku .ipynb, kod oraz opis i część merytoryczna (bez umieszczania zewnętrznych obrazów lub grafów). Szata graficzna pliku głównego również nie jest istotną kwestią - proszę się inspirować tym, jak zbudowany jest ten plik i w jaki sposób definiowane są rozdziały (elementy markdown) plus kod i jego opis."
]
},
{
"cell_type": "markdown",
"id": "62e06354-7b84-46af-a431-1c65100e4a05",
"metadata": {},
"source": [
"**1. Wstęp i definicja problemu**\n",
"- Definicja i opis problemu - co ma robić system i dla kogo? Jaka jest motywacja (dlaczego problem jest wart rozwiązania), \n",
"- **Hipoteza robocza:** grupa przypuszcza, że z danych X da się przewidzieć/rozpoznać Y z sensowną skutecznością.\n",
"- Krótki opis kryteriów sukcesu (co znaczy „działa\"?).\n",
"\n",
"**2. Dane: pozyskanie, opis i obróbka wstępna**\n",
"\n",
"***2.1. Źródło i sposób pozyskania danych***\n",
"- Skąd dane: własny pomiar (telefon/kamera/dyktafon), ankieta, zbiór publiczny, połączenie?\n",
"- Kwestie etyczne i prawne: zgody osób, anonimizacja, licencje zbiorów publicznych.\n",
"\n",
"***2.3. Analiza eksploracyjna (EDA)***\n",
"- Rozmiar zbioru, liczba przykładów na klasę (bilans klas), statystyki opisowe, rozkłady cech (histogramy, boxploty), skosność, korelacje między cechami / redundancja oraz wizualizacja danych (przykładowe obrazy/nagrania, scatter ploty, PCA/t-SNE jako rzut 2D).\n",
"\n",
"***2.4. Czyszczenie i przygotowanie***\n",
"- Braki danych: ile, gdzie, jak obsłużone (usuwanie vs imputacja + uzasadnienie), duplikaty i anomalie, wartości odstające (jak wykryte czy usunięte i dlaczego była to słuszna decyzja?), skalowanie/normalizacja cech, kodowanie zmiennych kategorycznych (one-hot, label encoding).\n",
"\n",
"***2.5. Podział danych***\n",
"- Zbiór treningowy / walidacyjny / testowy - proporcje i sposób podziału, jak uniknięto przecieku informacji ze zbioru testowego do treningowego?\n",
"\n",
"**3. Reprezentacja danych i inżynieria cech**\n",
"- Jak surowe dane zamieniono na wejście modelu, ręcznie zaprojektowane cechy (jeśli są - skąd pomysł, dlaczego), czy i jak zastosowano redukcję wymiarowości, co ona dała. Czy stosowano augmentację danych? Jakie transformacje zastosowano (uzasadnienie), czy było balansowanie klas (próbkowanie, wagi klas)?\n",
"\n",
"**4. Strategia uczenia i wybór modelu**\n",
"\n",
"***4.1. Uzasadnienie wyboru rodzaju uczenia***\n",
"- Nadzorowane / nienadzorowane / dlaczego pasuje do problemu i danych, uczenie od zera czy transfer learning (motywacja i sposób)\n",
"\n",
"***4.2. Kandydaci i wybór finalny***\n",
"- Lista rozważanych modeli, kryteria wyboru: typ danych, rozmiar zbioru, interpretowalność, koszt obliczeniowy, literatura. Finalna architektura/rodzaj modelu + hiperparametry.\n",
"\n",
"***4.3. Trenowanie***\n",
"- Jak wyglądało trenowanie, funkcje straty, optymalizator, liczba epok/iteracji, walidacja krzyżowa, augmentacja w treningu - czy model trenowano też na danych augmentowanych (porównanie z/bez)?\n",
"\n",
"**5. Ocena jakości modelu**\n",
"\n",
"***5.1. Mierniki***\n",
"- Dobór mierników do problemu: accuracy, precision/recall, F1, macierz pomyłek, ROC/AUC, MAE/RMSE. Uzasadnienie: dlaczego akurat te? (np. przy nierównych klasach accuracy bywa mylące).\n",
"\n",
"***5.2. Uczciwość oceny***\n",
"- Czy dane testowe są nowa dla modelu? Analiza błędów: gdzie model może się mylić? (macierz pomyłek, przykłady błędnych klasyfikacji). To może być najciekawsza część całego projektu)\n",
"\n",
"***5.3. Odporność modelu (jeśli dotyczy)***\n",
"- Wyniki na danych zaszumionych / augmentowanych / z innej dystrybucji (inny telefon, inne światło, inna pora dnia) + porównanie: model wytrenowany z augmentacją vs bez.\n",
"\n",
"**6. Wyniki i interpretacja**\n",
"- Podsumowanie wyników, interpretacja (które cechy/fragmenty danych miały największy wpływ, współczynniki, ważność cech, Grad-CAM. Czy potwierdziła się hipoteza robocza. Niespodzianki podczas pracy - czego się autorzy nie spodziewali, nie zauważyli lub nie brali pod uwagę, ale wyszło podczas pracy?\n",
"\n",
"**7. Wnioski, ograniczenia i dalszy rozwój**\n",
"- Czy cel został osiągnięty? Czy rozwiązanie jest użyteczne w praktyce? Jakie były ograniczenia: zbyt mały zbiór, jedno urządzenie, jedna pora roku, ryzyko przetrenowania, obciążenie etykiet. Co można byłoby zrobić inaczej, gdyby było więcej czasu, więcej danych?\n",
"\n",
"**8. Bibliografia i źródła**\n",
"- Zbiory danych publicznych (linki, licencje), literatura / dokumentacja bibliotek / tutoriale, które rzeczywiście pomogły."
]
},
{
"cell_type": "markdown",
"id": "5f27142f-f3db-4bd8-baef-383ce3d3c5ba",
"metadata": {},
"source": [
"# Ćwiczenia"
]
},
{
"cell_type": "markdown",
"id": "fa0776c2-52c1-400d-970b-08756d74719f",
"metadata": {},
"source": [
"Niniejsze ćwiczenia są zorganizowane w blokach:\n",
"\n",
"1. **Dane tabularyczne** - analiza statystyczna, rozkłady, korelacje, grupowanie (np. problem regresji/klasyfikacji),\n",
"2. **Czyszczenie danych** - braki, duplikaty, outlierzy, normalizacja,\n",
"3. **Analiza obrazów** - obraz jako macierz liczb, prostsze cechy obrazu,\n",
"4. **Augmentacja danych** - co robić, gdy danych jest mało (lub chcemy sprawdzić jakość modelu),\n",
"5. **Kodowanie zmiennych kategorycznych i inżynieria cech** - sposób kodowania danych (np. tekstowych) oraz tworzenie nowych cech z danych,\n",
"6. **Redukcja wymiarowości** - PCA i t-SNE."
]
},
{
"cell_type": "markdown",
"id": "c8664f90-4dc7-4ca9-834d-7428b5eddc80",
"metadata": {},
"source": [
"Każde zadanie zawiera: opis, gotowy kod do uruchomienia (można a wręcz należy go modyfikować!) oraz miejsce na **refleksję**, czyli krótki opis własnych spostrzeżeń i przemyśleń.\n",
"\n",
"To nie jest test - nie ma \"*złych odpowiedzi*\", liczy się spostrzegawczość, uważne patrzenie na dane, wyciąganie logicznych wniosków oraz wyrobienie sobie intuicji przy pracy z danymi. Celem jest przetrenowanie najważniejszych kwestii dotyczących pracy z surowymi danymi (przed wyborem modelu i rozpoczęciem trenowania), oraz inspiracja do kluczowych elementów projektu."
]
},
{
"cell_type": "markdown",
"id": "6079b327-7509-4a43-aff3-d59c5fc7748b",
"metadata": {},
"source": [
"**Jak pracować z tym plikiem?** \n",
"\n",
"Komórki należy uruchamiać po kolei, można eksperymentować ze zmianą parametrów, a wnioski należy wpisać w komórkach oznaczonych **REFLEKSJA**. Uzupełniony, kompletny (uruchomienie wszystkich elementów kodu + wnioski) plik należy umieścić w **swoim** repozytorium na git.michalkasprowicz.com"
]
},
{
"cell_type": "markdown",
"id": "10aeeb7c-fa6d-4ec2-890b-6e509845c15d",
"metadata": {},
"source": [
"## Praca z danymi i połączenie z notatnikiem."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99c315db-b713-4c9e-9ae6-c9dff5113eae",
"metadata": {},
"outputs": [],
"source": [
"# jeśli notebook odpalamy w google colab:\n",
"\n",
"#from google.colab import drive\n",
"#drive.mount('/content/drive')\n",
"#df = pd.read_csv('/content/drive/MyDrive/sciezka/do/pliku.csv')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a167b12c-b58d-4fc8-bb91-90932935ba21",
"metadata": {},
"outputs": [],
"source": [
"# jeśli odpalamy poza google colab, ale plik z google drive jest dostępny publicznie (lub \"każdy z linkiem\")\n",
"#import pandas as pd\n",
"\n",
"# Zamień w linku \"view\" lub uc?id= na format do pobrania\n",
"#file_id = 'XXXX'\n",
"#url = f'https://drive.google.com/uc?export=download&id={file_id}'\n",
"#df = pd.read_csv(url)"
]
},
{
"cell_type": "markdown",
"id": "c8f2ec46-0da1-4781-8c3f-26c4fee28d6a",
"metadata": {},
"source": [
"## Część 1: Analiza danych tabularycznych\n",
"\n",
"### Zadanie 1.1\n",
"#### Pierwsze spojrzenie na dane: Iris\n",
"\n",
"**Problem:** klasyczny zbiór danych z pomiarami irysów (kwiatów). Trzy gatunki, cztery cechy liczbowe. To typowy problem **klasyfikacji** - na podstawie pomiarów przewidujemy gatunek."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9daa27d5-91f0-4646-a4cb-e1de74784c48",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"from sklearn.datasets import load_iris\n",
"\n",
"iris_raw = load_iris(as_frame=True)\n",
"iris = iris_raw.frame # DataFrame z cechami i kolumną target\n",
"iris['species'] = iris['target'].map(dict(enumerate(iris_raw.target_names)))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ee94bea-41fb-4f74-966b-fb44beca98d3",
"metadata": {},
"outputs": [],
"source": [
"# Zawsze zaczynamy od: ile wierszy i kolumn? Jakie typy? Czy są braki?\n",
"print(iris.shape)\n",
"iris.info()\n",
"iris.head()"
]
},
{
"cell_type": "markdown",
"id": "ad835294-4868-42b6-a981-ffee005bdae5",
"metadata": {},
"source": [
"**Na co patrzeć przy pierwszym uruchomieniu:**\n",
"- `shape` → liczba obserwacji (wierszy) i cech (kolumn),\n",
"- `info()` → typy kolumn (liczbowe vs tekstowe) i liczba braków (`non-null`),\n",
"- `head()` → czy wartości wyglądają sensownie (jednostki, skala, dziwne symbole)."
]
},
{
"cell_type": "markdown",
"id": "7c1624a2-0db6-4bff-be79-ab383f50b097",
"metadata": {},
"source": [
"### Zadanie 1.2\n",
"#### Statystyki opisowe i rozkłady kolumn"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5562f423-16ad-44de-8825-e3786ce60680",
"metadata": {},
"outputs": [],
"source": [
"# Statystyki opisowe dla kolumn numerycznych\n",
"iris.describe()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "882c1955-a934-489d-8e3c-ad0c75d5bef3",
"metadata": {},
"outputs": [],
"source": [
"# Histogramy wszystkich cech – podzielone według gatunku\n",
"features = ['sepal length (cm)', 'sepal width (cm)',\n",
" 'petal length (cm)', 'petal width (cm)']\n",
"\n",
"fig, axes = plt.subplots(2, 2, figsize=(12, 8))\n",
"for ax, col in zip(axes.ravel(), features):\n",
" sns.histplot(data=iris, x=col, hue='species', kde=True, ax=ax)\n",
" ax.set_title(col)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "d22a022c-5500-4fbb-9087-b1b5443fe564",
"metadata": {},
"source": [
"Skośność i kurtoza to dwie miary statystyczne używane do opisu kształtu rozkładu bez konieczności jego wykreślania. Mówiąc dokładniej, skośność wskazuje stopień symetrii (lub skośności) rozkładu, podczas gdy kurtoza wskazuje stopień koncentracji rozkładu wokół jego średniej.\n",
"\n",
"**Przykład**:\n",
"W badaniach nad lękiem przed egzaminami u studentów większość wyników może skupiać się w dolnej części skali (niski poziom lęku), podczas gdy mniejsza część studentów doświadcza wysokiego poziomu lęku, co skutkuje pozytywną skośnością rozkładu."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b577695a-7585-40fa-9fae-89cbcc49ea8c",
"metadata": {},
"outputs": [],
"source": [
"# Skosność rozkładów - miara asymetrii (0 = symetryczny rozkład)\n",
"for col in features:\n",
" print(f\"{col:25s} skośność = {iris[col].skew():+.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "3d1393a5-a089-4e4a-9b4f-b6def1a869fe",
"metadata": {},
"source": [
"**Interpretacja skosności:** wartość bliska 0 → rozkład symetryczny; dodatnia → długi \"ogon\" po prawej stronie (wartości ekstremalne duże); ujemna → ogon po lewej. Skosność ma znaczenie m.in. przy wyborze sposobu uzupełniania braków (średnia vs mediana)."
]
},
{
"cell_type": "markdown",
"id": "4c5d890e-c49a-4861-acc2-d87fd7cac515",
"metadata": {},
"source": [
"**Wykres pudełkowy** - forma graficznej prezentacji rozkładu cechy statystycznej. Pozwala ująć na jednym rysunku wiadomości dotyczące położenia, rozproszenia i kształtu rozkładu empirycznego badanej cechy."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ccca3e5-588b-4f32-a4fe-ce149db8f763",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: wykres pudełkowy cech z podziałem na gatunki\n",
"fig, axes = plt.subplots(1, 4, figsize=(14, 4))\n",
"for ax, col in zip(axes, features):\n",
" sns.boxplot(data=iris, x='species', y=col, ax=ax)\n",
" ax.tick_params(axis='x', rotation=30) # obrót etykiet osi X\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "bc1a3fbb-5fc5-4688-b9d7-0b30016a3905",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Która cecha najlepiej rozdziela gatunki (patrząc na histogramy i boxploty)? Która praktycznie nic nie wnosi? Czy widać punkty odstające (outliery) na wykresach pudełkowych? Czy na tym zbiorze będzie łatwo czy trudno dokonać klasyfikacji gatunków?"
]
},
{
"cell_type": "markdown",
"id": "1405d77a-882e-4b31-9d8d-fbbdd7a6581a",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "9720ec83-3130-48de-90f9-d213e9409c55",
"metadata": {},
"source": [
"### Zadanie 1.3\n",
"#### Korelacje między zmiennymi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0852218a-1852-4f94-8514-c5d24d42e1af",
"metadata": {},
"outputs": [],
"source": [
"# Macierz korelacji (dotyczy tylko kolumn numerycznych)\n",
"plt.figure(figsize=(6, 5))\n",
"sns.heatmap(iris[features].corr(), annot=True, cmap='coolwarm',\n",
" vmin=-1, vmax=1, fmt='.2f')\n",
"plt.title('Korelacja cech – Iris')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbf354b4-b772-4f03-b099-ab646e7c2b80",
"metadata": {},
"outputs": [],
"source": [
"# Wykres rozrzutu (scatter plot) pary najbardziej skorelowanych cech\n",
"sns.scatterplot(data=iris, x='petal length (cm)', y='petal width (cm)',\n",
" hue='species')\n",
"plt.title('Długość vs szerokość płatka')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "7a249e58-45be-440b-bd95-91bfd09608ea",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Które pary cech są silnie skorelowane? Dlaczego silna korelacja między dwiema cechami bywa problemem dla niektórych algorytmów? *(podpowiedź: redundancja danych)*"
]
},
{
"cell_type": "markdown",
"id": "57d0744f-3c12-4192-858f-c58fa384a40f",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "a99c3545-b41a-4b37-a7b3-2fcef7ef000a",
"metadata": {},
"source": [
"### Zadanie 1.4\n",
"#### Wybrakowane dane: Titanic\n",
"\n",
"**Problem:** pasażerowie statku Titanic. Zbiór zawiera braki, kolumny tekstowe i różne skale. Idealny do przećwiczenia realnej pracy z danymi."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d6ebc6b-d921-42bb-9589-accde5514d8f",
"metadata": {},
"outputs": [],
"source": [
"titanic = sns.load_dataset('titanic')\n",
"titanic.shape"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74c374ae-0f22-42e6-b014-348eb9b98185",
"metadata": {},
"outputs": [],
"source": [
"titanic.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7551f789-bf49-45bd-ba59-e9e526a6ef78",
"metadata": {},
"outputs": [],
"source": [
"# Liczba i procent braków w każdej kolumnie - to zawsze pierwszy krok\n",
"braki = titanic.isnull().sum()\n",
"procent = 100 * braki / len(titanic)\n",
"pd.DataFrame({'braki': braki, 'procent': procent.round(1)})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "33ce6d8a-62c2-4db5-86f0-3b53ca352d8d",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: analiza zmiennej docelowej (survived) względem innych kolumn\n",
"# (observed=True -> grupujemy tylko po wartościach faktycznie obecnych w danych)\n",
"print(titanic.groupby('class', observed=True)['survived'].mean())\n",
"print()\n",
"print(titanic.groupby('sex', observed=True)['survived'].mean())\n",
"\n",
"sns.barplot(data=titanic, x='class', y='survived', hue='sex')\n",
"plt.title('Szansa przeżycia wg klasy i płci')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "dd53a276-cdf0-410c-8edc-43be261ed797",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Które kolumny mają najwięcej braków? Jaki wzorzec przeżywalności widać w danych (płeć, klasa)? Jeżeli tworzylibyśmy model przewidujący przeżycie pasażera - które 2-3 cechy moglibyśmy wybrać na podstawie tej analizy i dlaczego?"
]
},
{
"cell_type": "markdown",
"id": "a60098b6-d685-443d-9e97-f7bcfe97e02e",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "d7ba2ff2-8643-4d9b-ae96-1525785d0f07",
"metadata": {},
"source": [
"### Zadanie 1.5\n",
"#### Podgląd grupowania: KMeans bez etykiet\n",
"\n",
"Czasem nie mamy etykiet (np. ankiety, profile klientów, zdarzenia z czujników) - wtedy stosujemy uczenie **nienadzorowane**. Proszę sprawdzić, czy algorytm KMeans \"odkryje\" gatunki irysów samodzielnie."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f74f5ad-ecb0-4bc6-a472-8343ad4a7159",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.cluster import KMeans\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
"X = iris[features]\n",
"X_scaled = StandardScaler().fit_transform(X) # skalowanie - dlaczego?, wyjaśnienie w części 2\n",
"\n",
"kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)\n",
"clusters = kmeans.fit_predict(X_scaled)\n",
"\n",
"# Porównanie: prawdziwe gatunki vs grupy znalezione przez algorytm\n",
"tabela = pd.crosstab(iris['species'], clusters,\n",
" rownames=['gatunek'], colnames=['KMeans'])\n",
"print(tabela)"
]
},
{
"cell_type": "markdown",
"id": "d431d126-2fd8-4438-9ab9-5d1e12c9e380",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Czy klastry odpowiadają gatunkom? Który gatunek jest mylony z innym? Do zastanowienia się: gdyby etykiety nie istniały, co moglibyśmy powiedzieć o danych na podstawie samych klastrów?"
]
},
{
"cell_type": "markdown",
"id": "91a3ad1e-5942-494e-8ad3-ccb5c4160344",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "f325eaef-3350-42df-9792-7340f600fce8",
"metadata": {},
"source": [
"## Część 2: Czyszczenie i przygotowanie danych\n",
"\n",
"Realne dane (w tym dane, które zbiorą państwo sami w swoich projektach!) prawie nigdy nie są czyste: brakujące pomiary, literówki, duplikaty, wartości odstające, różne skale. Tu celowo \"psujemy\" dane, żeby przećwiczyć diagnozę i naprawę."
]
},
{
"cell_type": "markdown",
"id": "3062d424-4290-447d-b754-c82459113bf3",
"metadata": {},
"source": [
"### Zadanie 2.1\n",
"#### Diagnoza: celowo uszkodzony zbiór"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4e2797e-e595-4cc3-9f73-58ae9f68dd51",
"metadata": {},
"outputs": [],
"source": [
"rng = np.random.default_rng(7)\n",
"\n",
"# Startujemy od iris, ale \"zabieramy\" etykiety i psujemy dane\n",
"broken = iris[features].copy()\n",
"\n",
"# 1) usuwamy losowo 8% wartości\n",
"mask = rng.random(broken.shape) < 0.08\n",
"broken[mask] = np.nan\n",
"\n",
"# 2) dodajemy skrajne wartości (błędy pomiaru / wpisy ręczne)\n",
"for col in ['sepal width (cm)', 'petal length (cm)']:\n",
" losowe_indeksy = rng.choice(broken.index, 5, replace=False)\n",
" broken.loc[losowe_indeksy, col] = broken[col].max() * 10\n",
"\n",
"# 3) duplikujemy kilka wierszy\n",
"broken = pd.concat([broken, broken.sample(15, random_state=1)])\n",
"broken = broken.reset_index(drop=True)\n",
"\n",
"broken.info()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7148cc45-632e-42b7-98da-261722b3eaa5",
"metadata": {},
"outputs": [],
"source": [
"# Zadanie diagnostyczne: znajdź wszystkie problemy!\n",
"print(\"Braki w kolumnach:\")\n",
"print(broken.isnull().sum())\n",
"print(\"\\nLiczba duplikatów:\", broken.duplicated().sum())\n",
"broken.describe()"
]
},
{
"cell_type": "markdown",
"id": "2c444410-f2e5-4abe-b6b8-1415a7e3f5ab",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Proszę wypisać wszystkie trzy rodzaje problemów (podpowiedź: `count` vs liczba wierszy, `max` w `describe()`, liczba duplikatów). Jak każdy z tych problemów mógłby **wpłynąć na wytrenowany model**?"
]
},
{
"cell_type": "markdown",
"id": "644b2ab7-6b54-4a87-b85b-85a4084d026e",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "e1acd580-70c8-421e-a89a-df9aba4e0e16",
"metadata": {},
"source": [
"### Zadanie 2.2\n",
"#### Braki danych: usuwanie vs uzupełnianie"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddd012b5-690a-4224-8f83-b0ef3b0dadc0",
"metadata": {},
"outputs": [],
"source": [
"# Sposób A: usunięcie wierszy z brakami\n",
"drop_rows = broken.dropna()\n",
"print(\"A) dropna:\", drop_rows.shape, \"-> tracimy\",\n",
" len(broken) - len(drop_rows), \"wierszy\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79849575-d433-489c-8e1c-5ffc164c1210",
"metadata": {},
"outputs": [],
"source": [
"# Sposób B: uzupełnienie medianą (medianą, bo dane mogą być skośne lub mieć wartości odstające)\n",
"filled = broken.fillna(broken.median())\n",
"print(\"B) fillna(mediana):\", filled.shape, \"braki:\", filled.isnull().sum().sum())\n",
"\n",
"filled.describe()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8e5e7cbd-c05a-440f-b980-09fa200cac1e",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: proszę porównać medianę cechy w danych oryginalnych i \"uszkodzonych\"\n",
"for col in features:\n",
" print(f\"{col:25s} mediana oryginał: {iris[col].median():7.2f} | \"\n",
" f\"mediana uszkodzone: {broken[col].median():7.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "ea360cca-1f17-466c-bf1a-606e48cea53b",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Kiedy usunięcie wierszy jest dopuszczalne, a kiedy stracilibyśmy zbyt dużo danych? Dlaczego wypełniamy braki **medianą**, a nie średnią? *(podpowiedź: jak jedna wartość 10× większa od maksimum zmienia średnią i jak zmienia medianę)*"
]
},
{
"cell_type": "markdown",
"id": "15a9d7a2-4382-44e2-bd15-8879a007a13f",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "88497d3c-2c06-4a16-be14-f0473e88006d",
"metadata": {},
"source": [
"### Zadanie 2.3\n",
"#### Obserwacje odstające (outliers): metoda IQR - (Interquartile range; rozstęp ćwiartkowy, rozstęp międzykwartylowy)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4b139b83-94bb-475e-905c-6f76444ceda0",
"metadata": {},
"outputs": [],
"source": [
"# Metoda IQR: outlier = wartość poniżej Q1-1.5*IQR lub powyżej Q3+1.5*IQR\n",
"Q1 = filled.quantile(0.25)\n",
"Q3 = filled.quantile(0.75)\n",
"IQR = Q3 - Q1\n",
"\n",
"jest_outlier = (filled < Q1 - 1.5 * IQR) | (filled > Q3 + 1.5 * IQR)\n",
"print(\"Liczba outlierów w każdej kolumnie:\")\n",
"print(jest_outlier.sum())\n",
"\n",
"# Oczyszczone dane: usuwamy TYLKO wiersze zawierające outliery\n",
"clean = filled[~jest_outlier.any(axis=1)]\n",
"print(\"\\nPrzed czyszczeniem:\", len(filled), \"-> Po czyszczeniu:\", len(clean))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7648047c-e012-4e4f-a09b-799130deb10d",
"metadata": {},
"outputs": [],
"source": [
"# Wizualna kontrola: boxploty przed i po\n",
"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
"sns.boxplot(data=filled, ax=axes[0])\n",
"axes[0].set_title('Przed usunięciem outlierów')\n",
"sns.boxplot(data=clean, ax=axes[1])\n",
"axes[1].set_title('Po usunięciu outlierów')\n",
"for ax in axes:\n",
" ax.tick_params(axis='x', rotation=30)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "3b5bb538-bf89-41b5-8049-ff0c1519fea6",
"metadata": {},
"source": [
"**Uwaga merytoryczna:** nie każdy outlier to błąd! Ekstremalna cena mieszkania, rzadka choroba czy nietypowe wskazanie czujnika mogą być **najważniejszymi obserwacjami w zbiorze**. Przed usunięciem takich wartości, warto zastanowić się, czy to błąd pomiaru, czy prawdziwa, rzadka, wartość."
]
},
{
"cell_type": "markdown",
"id": "e42a800e-2a75-4052-b9da-acff38ed1617",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Ile wierszy usunął filtr IQR? Proszę podać przykład (np. z jednego z tematów projektów: aktywność fizyczna, czas przejazdu, jakość zdjęć), w którym usunięcie outlierów poprawiłoby model, i przykład, w którym mogło by go zepsuć."
]
},
{
"cell_type": "markdown",
"id": "fea6d6d1-6898-4796-adb9-3c5a34ae11a9",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "b265a002-5d97-461b-84f3-76ad819b2f08",
"metadata": {},
"source": [
"### Zadanie 2.4\n",
"#### Normalizacja i standaryzacja\n",
"\n",
"Algorytmy oparte na odległości (KNN, KMeans, sieci neuronowe) są wrażliwe na skalę cech. Kolumna \"*wiek (0 - 100)*\" i \"*dochód (0 - 100 000)*\" nie mogą konkurować na równych prawach."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37fd4e7a-aa09-4ead-afd4-a929afd2cacc",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.preprocessing import StandardScaler, MinMaxScaler\n",
"\n",
"demo = pd.DataFrame({\n",
" 'wiek': [22, 25, 30, 35, 40, 55, 60],\n",
" 'dochod_tys': [3.5, 4.2, 5.0, 6.1, 7.3, 9.8, 12.5],\n",
" 'plan_PKB': [55.2, 61.7, 58.9, 72.1, 69.4, 80.3, 77.8]\n",
"})\n",
"\n",
"standard = pd.DataFrame(StandardScaler().fit_transform(demo),\n",
" columns=demo.columns, index=demo.index) # średnia=0, odch=1\n",
"minmax = pd.DataFrame(MinMaxScaler().fit_transform(demo),\n",
" columns=demo.columns, index=demo.index) # zakres [0, 1]\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(14, 4))\n",
"sns.boxplot(data=demo, ax=axes[0])\n",
"axes[0].set_title('Surowe dane')\n",
"sns.boxplot(data=standard, ax=axes[1])\n",
"axes[1].set_title('Standaryzacja (z-score)')\n",
"sns.boxplot(data=minmax, ax=axes[2])\n",
"axes[2].set_title('Normalizacja Min-Max')\n",
"for ax in axes:\n",
" ax.tick_params(axis='x', rotation=15)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"Zakresy kolumn surowych:\\n\", demo.max() - demo.min())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae2adddd-8dbf-4b9f-8269-332000cd21b8",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: wpływ skalowania na odległość między dwoma obserwacjami\n",
"p1, p2 = demo.iloc[0], demo.iloc[6]\n",
"odleglosc_surowa = np.linalg.norm(p1 - p2)\n",
"odleglosc_skala = np.linalg.norm(standard.iloc[0] - standard.iloc[6])\n",
"print(f\"Odległość surowa: {odleglosc_surowa:.2f}\")\n",
"print(f\"Odległość po standaryzacji: {odleglosc_skala:.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "148d33e5-13dc-476e-9dd4-fa2c47364a1f",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Zakres `plan_PKB` (ok. 25) i `dochod_tys` (ok. 9) - która cecha \"*dominowała*\" odległość w danych surowych? Proszę wyjaśnić własnymi słowami, dlaczego bez skalowania KMeans prawie ignorowałby różnice wieku między osobami. Kiedy wybra Min-Max, a kiedy z-score? *(podpowiedź: co się stanie z Min-Max, gdy w danych pojawi się jeden ekstremalny outlier?)*\n"
]
},
{
"cell_type": "markdown",
"id": "38c28d55-3dca-4db8-8822-eb60ea6e5c79",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "da4b9023-61b8-42e7-8fae-5a165f996474",
"metadata": {},
"source": [
"## Część 3: Analiza obrazów\n",
"\n",
"Projekty takie jak rozpoznawanie gestów, dojrzałości owoców, gatunków drzew czy jakości zdjęć działają na obrazach. Kluczowa idea: **obraz to zwykła macierz liczb** (*dla obrazu w skali szarości*) lub trzy macierze (RGB)."
]
},
{
"cell_type": "markdown",
"id": "c8faa957-53a0-4366-b915-c65b0c65d53e",
"metadata": {},
"source": [
"### Zadanie 3.1\n",
"#### Obraz jako macierz"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "639671cb-756e-44a2-ab48-a3fcc76ba4b0",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.datasets import load_digits\n",
"\n",
"digits = load_digits()\n",
"X_img = digits.images # 1797 obrazów 8x8\n",
"X_flat = digits.data # to samo, \"spłaszczone\" do 64 cech\n",
"y = digits.target\n",
"\n",
"print(X_img.shape, X_flat.shape)\n",
"print(\"\\nPierwsza cyfra (prawdziwa wartość:\", y[0], \") jako macierz:\")\n",
"print(X_img[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "269f1deb-7f56-4d54-b95b-c78599eb3635",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(1, 8, figsize=(12, 2))\n",
"for i, ax in enumerate(axes):\n",
" ax.imshow(X_img[i], cmap='gray_r')\n",
" ax.set_title(y[i])\n",
" ax.axis('off')\n",
"plt.suptitle('Pierwsze 8 cyfr ze zbioru digits')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "9068645a-6e76-4886-a967-6990dddf23c9",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Proszę spojrzeć na wydrukowaną macierz pierwszej cyfry: jak na podstawie samych liczb (0–16) \"*wygląda*\" ciemny piksel, a jak jasny? Dlaczego `X_img.shape` = (1797, 8, 8), a `X_flat.shape` = (1797, 64)?"
]
},
{
"cell_type": "markdown",
"id": "91567b38-30ba-4fa0-8963-4f43266e5ef2",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "5a783593-bd2a-4ec5-b9ad-2747c671267a",
"metadata": {},
"source": [
"### Zadanie 3.2\n",
"#### Statystyka pikseli jako prosta \"cecha\"\n",
"\n",
"Zamiast wszystkich 64 pikseli, policzmy po **jednej liczbie na obraz** - średnią jasność."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18d540bd-0535-46fe-b908-1518fccc5fdd",
"metadata": {},
"outputs": [],
"source": [
"jasnosc = X_flat.mean(axis=1) # średnia z 64 pikseli = 1 cecha\n",
"\n",
"df = pd.DataFrame({'jasnosc': jasnosc, 'cyfra': y})\n",
"sns.boxplot(data=df, x='cyfra', y='jasnosc')\n",
"plt.title('Średnia jasność obrazu w zależności od cyfry')\n",
"plt.show()\n",
"\n",
"print(df.groupby('cyfra')['jasnosc'].mean().round(2))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7adaa26-b1bd-42cf-9a7b-670e2b6042f7",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: proszę dodać drugą prostą cechę, np. liczbę \"ciemnych\" pikseli (< 5)\n",
"ciemne = (X_flat < 5).sum(axis=1)\n",
"\n",
"plt.figure(figsize=(7, 5))\n",
"sns.scatterplot(x=jasnosc, y=ciemne, hue=y, palette='tab10', legend='full',\n",
" s=15, alpha=0.6)\n",
"plt.xlabel('średnia jasność')\n",
"plt.ylabel('liczba ciemnych pikseli')\n",
"plt.title('Cyfry w przestrzeni DWÓCH ręcznie zaprojektowanych cech')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "57cffeee-51f8-4ee0-bead-797b7d0c22f7",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Czy sama średnia jasność wystarczy, by odróżnić np. cyfrę 0 od 1? A cyfrę 1 od 7? Które cyfry rozdzieliły się ładnie już w przestrzeni dwóch prostych cech, a które się mieszają? To jest właśnie sens **reprezentacji danych**: te same informacje, inny układ czyli inna (łatwiejsza/trudniejsza) separowalność."
]
},
{
"cell_type": "markdown",
"id": "d0a9da71-e1c1-4782-86cb-6d833b2ba5f6",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "4c9df693-3e0c-476d-9cd7-062bf949cd69",
"metadata": {},
"source": [
"### Zadanie 3.3 \n",
"#### (opcjonalnie) własny obraz\n",
"\n",
"Jeśli pracujecie w Google Colab - należy wgrać własne zdjęcie. UWAGA! Kod bez podanej poprawnej nazwy pliku wygeneruje błąd!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aeb848fd-f566-42ba-8bca-0aa8c5ace4fd",
"metadata": {},
"outputs": [],
"source": [
"# W Colab: from google.colab import files; files.upload()\n",
"from PIL import Image\n",
"\n",
"img = Image.open('nazwa_pliku.jpg').convert('L') # L = skala szarości\n",
"arr = np.asarray(img, dtype=float)\n",
"\n",
"print(\"Rozmiar obrazu:\", arr.shape, \"| min:\", arr.min(), \"| max:\", arr.max())\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(13, 4))\n",
"axes[0].imshow(arr, cmap='gray')\n",
"axes[0].set_title('Obraz')\n",
"axes[1].hist(arr.ravel(), bins=50)\n",
"axes[1].set_title('Histogram jasności')\n",
"axes[2].imshow(arr[:32, :32], cmap='gray_r')\n",
"axes[2].set_title('Powiększony fragment')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "5824809c-0210-4bb7-b1dd-ebb4699de2bb",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Co mówi histogram jasności o zdjęciu (jasne ciemne? kontrast?). Jeśli Wasza grupa projektowa zbiera zdjęcia (owoców, liści, produktów) - jakie różnice w histogramach można spodziewać się między zdjęciami robionymi w dzień i wieczorem?"
]
},
{
"cell_type": "markdown",
"id": "8b8016bc-0d80-4172-8b1a-61ee1ee2be92",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "f8b4db74-f3c6-4e25-b6f0-5675d3ecd4c6",
"metadata": {},
"source": [
"## Część 4: Augmentacja danych\n",
"\n",
"**Problem:** w projektach własnych (zdjęcia gestów, owoców, liści, produkty) danych jest zwykle za mało, np. 20 - 50 zdjęć na klasę zamiast tysięcy. Rozwiązaniem bywa **augmentacja**: generowanie nowych, zmodyfikowanych kopii istniejących przykładów, które zachowują etykietę.\n",
"\n",
"**Dodatkowa korzyść**: dodając szum do danych, możemy przetestować odporność modelu na realne zakłócenia (słabsze światło, poruszoną kamerę, źle napisaną cyfrę)."
]
},
{
"cell_type": "markdown",
"id": "26b88abf-c172-485a-82b2-2c878b39a409",
"metadata": {},
"source": [
"### Zadanie 4.1\n",
"#### Transformacje geometryczne i szum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3739bc01-f066-4423-9464-00f4d40e117e",
"metadata": {},
"outputs": [],
"source": [
"from scipy.ndimage import rotate, shift\n",
"\n",
"obraz = X_img[0]\n",
"etykieta = y[0]\n",
"\n",
"przyklady = {\n",
" 'oryginał': obraz,\n",
" 'obrót +20°': rotate(obraz, 20, reshape=False),\n",
" 'obrót -20°': rotate(obraz, -20, reshape=False),\n",
" 'przesunięcie': shift(obraz, (1, 2)),\n",
"}\n",
"\n",
"fig, axes = plt.subplots(1, len(przyklady), figsize=(12, 3))\n",
"for ax, (nazwa, im) in zip(axes, przyklady.items()):\n",
" ax.imshow(im, cmap='gray_r')\n",
" ax.set_title(f\"{nazwa}\\netykieta: {etykieta}\")\n",
" ax.axis('off')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "194325be-2622-4646-9b0c-1388a0138bc7",
"metadata": {},
"source": [
"**Kluczowa zasada:** transformacja zmienia **piksele**, ale nie **etykietę**. Obrócona cyfra 0 to nadal cyfra 0. Dzięki temu jednym zamachem tworzymy wiele przykładów treningowych."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a27ba06f-25d0-442d-9fb5-da39dc283b6e",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: szum \"sól i pieprz\" oraz szum gaussowski\n",
"obrazek = X_flat[0].reshape(8, 8).astype(float)\n",
"\n",
"# (a) szum gaussowski (rozmycie/pogorszenie jakości sensora)\n",
"z_gaussem = obrazek + np.random.default_rng(1).normal(0, 2.5, obrazek.shape)\n",
"\n",
"# (b) sól i pieprz: losowe białe/czarne piksele (zakłócenia transmisji)\n",
"sol = obrazek.copy()\n",
"rng = np.random.default_rng(2)\n",
"maska = rng.random(sol.shape) < 0.10\n",
"sol[maska] = rng.choice([0, 16], size=maska.sum())\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(10, 3.5))\n",
"for ax, (nazwa, im) in zip(axes, [('oryginał', obrazek),\n",
" ('szum gaussowski', z_gaussem),\n",
" ('sól i pieprz', sol)]):\n",
" ax.imshow(im, cmap='gray_r')\n",
" ax.set_title(nazwa)\n",
" ax.axis('off')\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "7bdd1ba7-6fe3-471a-a8dd-5f1ec9017a95",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Które przekształcenia zachowały czytelność cyfry, a które ją zniszczyły? Przy jakim typie szumu człowiek jeszcze rozpozna cyfrę, a przy jakim już nie? Uwaga: **model może być znacznie wrażliwszy niż oko ludzkie** dlatego testuje się go na zaszumionych danych."
]
},
{
"cell_type": "markdown",
"id": "825e8dfd-b40d-488e-8bba-e08ac018c086",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "6eb1a14b-32ef-4204-931f-d6f20fd31130",
"metadata": {},
"source": [
"### Zadanie 4.2\n",
"#### Eksperyment: augmentacja a jakość modelu\n",
"\n",
"Sprawdźmy w praktyce dwa scenariusze:\n",
"1. **Mało danych** - trenujemy na małym podzbiorze,\n",
"2. **Mało danych + augmentacja** - ten sam podzbiór rozszerzony o obroty.\n",
"\n",
"Jako model użyjemy prostego k-najbliższych sąsiadów (KNN) - zasada działania nie jest tu istotna, ważny jest efekt."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16ab4491-e4b9-4cee-adc6-c33ecce93407",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.neighbors import KNeighborsClassifier\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.metrics import accuracy_score\n",
"\n",
"rng = np.random.default_rng(0)\n",
"\n",
"# (1) mały zbiór treningowy: tylko 80 przykładów\n",
"idx_treningowe = rng.choice(len(X_flat), 80, replace=False)\n",
"X_tr, y_tr = X_flat[idx_treningowe], y[idx_treningowe]\n",
"\n",
"def ocena(X_train, y_train, X_test, y_test):\n",
" knn = KNeighborsClassifier(n_neighbors=5)\n",
" knn.fit(X_train, y_train)\n",
" return accuracy_score(y_test, knn.predict(X_test))\n",
"\n",
"# stały zbiór testowy (bez augmentacji)\n",
"pozostale = np.setdiff1d(np.arange(len(X_flat)), idx_treningowe)\n",
"idx_testowe = rng.choice(pozostale, 300, replace=False)\n",
"X_te, y_te = X_flat[idx_testowe], y[idx_testowe]\n",
"\n",
"acc_male = ocena(X_tr, y_tr, X_te, y_te)\n",
"\n",
"# (2) augmentacja: dla KAŻDEGO przykładu treningowego dokładamy obrócone kopie\n",
"katy = [-15, -10, -5, 5, 10, 15]\n",
"X_aug, y_aug = [X_tr], [y_tr]\n",
"for kat in katy:\n",
" X_obrocone = np.array([rotate(im.reshape(8, 8), kat,\n",
" reshape=False).ravel() for im in X_tr])\n",
" X_aug.append(X_obrocone)\n",
" y_aug.append(y_tr)\n",
"X_aug = np.vstack(X_aug)\n",
"y_aug = np.concatenate(y_aug)\n",
"\n",
"acc_aug = ocena(X_aug, y_aug, X_te, y_te)\n",
"\n",
"print(f\"Dokładność przy 80 przykładach: {acc_male:.2%}\")\n",
"print(f\"Dokładność przy 80 przykładach + augmentacja: {acc_aug:.2%} \"\n",
" f\"(przykładów treningowych: {len(X_aug)})\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb018e53-267e-4987-bce7-b8f783ffd190",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: test odporności - ta sama ocena, ale na zaszumionym zbiorze testowym\n",
"X_te_szum = X_te + rng.normal(0, 3.0, X_te.shape) # dodajemy szum do danych testowych\n",
"\n",
"acc_szum_bez = ocena(X_tr, y_tr, X_te_szum, y_te)\n",
"acc_szum_aug = ocena(X_aug, y_aug, X_te_szum, y_te)\n",
"\n",
"print(f\"Zaszumiony test, model bez augmentacji: {acc_szum_bez:.2%}\")\n",
"print(f\"Zaszumiony test, model z augmentacją: {acc_szum_aug:.2%}\")"
]
},
{
"cell_type": "markdown",
"id": "ca4d6d0c-0c8d-4457-aeb2-5ef9629efb90",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"O ile punktów procentowych poprawiła augmentacja w obu eksperymentach? Czy model trenowany na augmentowanych danych okazał się odporniejszy na szum? W kontekście projektu: jeśli Wasza grupa projektowa zbiera np. zdjęcia liści - jakie konkretne augmentacje (obrót? zmiana jasności? przycięcie?) byłyby sensowne, a jakie mogłyby **zmienić znaczenie przykładu** i zaszkodzić? *(np. czy obrót o 180° zawsze zachowuje etykietę \"liść klonu\"? a \"kciuk w górę\"?)*"
]
},
{
"cell_type": "markdown",
"id": "13e57635-dbad-4f44-9d88-3628b0a6aee5",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "9b5f05b5-85b0-4647-abc4-745a3a9c3ee9",
"metadata": {},
"source": [
"## Część 5: Kodowanie zmiennych kategorycznych i inżynieria cech"
]
},
{
"cell_type": "markdown",
"id": "bcfe3bf5-b894-4c63-8834-b580c8485075",
"metadata": {},
"source": [
"Wracamy do zbioru **Titanic**. Modele uczenia maszynowego (poza wyjątkami) potrafią przetwarzać wyłącznie **liczby** a w realnych danych mnóstwo informacji znajduje się w kolumnach tekstowych: płeć, port zaokrętowania, gatunek rośliny, typ aktywności, kategoria produktu. Musimy te informacje **zakodować liczbowo** - i to w taki sposób, aby nie wprowadzić fałszywych zależności.\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "3c111b07-03d1-41d1-8942-9e80ce56e69e",
"metadata": {},
"source": [
"### Zadanie 5.1\n",
"#### Label encoding: liczby zamiast etykiet\n",
"\n",
"Najprostsza możliwa metoda: każdej unikalnej wartości przypisujemy kolejną liczbę całkowitą."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa5a23ab-685c-44d1-ad03-f36ee7eaf40d",
"metadata": {},
"outputs": [],
"source": [
"titanic['sex']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b1580b3-e91e-45f1-b8d2-eb8845e21fc3",
"metadata": {},
"outputs": [],
"source": [
"# Ręcznie: dwie kategorie -> 0 i 1\n",
"titanic['sex_label'] = titanic['sex'].map({'male': 0, 'female': 1})\n",
"titanic[['sex', 'sex_label']].head(10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7389f8f9-b172-464f-af00-8aea28d17d7e",
"metadata": {},
"outputs": [],
"source": [
"# Automatycznie: sklearn.LabelEncoder robi to samo dla dowolnej liczby kategorii\n",
"from sklearn.preprocessing import LabelEncoder\n",
"\n",
"le = LabelEncoder()\n",
"titanic['embarked_label'] = le.fit_transform(titanic['embarked'].fillna('Unknown'))\n",
"\n",
"# Podgląd mapowania: jaka wartość dostała jaki numer?\n",
"mapowanie = dict(zip(le.classes_, le.transform(le.classes_)))\n",
"print(\"Mapowanie:\", mapowanie)"
]
},
{
"cell_type": "markdown",
"id": "469c1d1a-46fc-4578-b10e-41fb5a3486ff",
"metadata": {},
"source": [
"**Pułapka label encoding!** Metoda jest idealna dla **dwóch kategorii** (binarnych).\n",
"\n",
"Ale gdy kategorii jest więcej, numery wprowadzają **pozorny porządek**: jeśli `Cherbourg=0`, `Queenstown=1`, `Southampton=2`, to model może zacząć traktować Southampton jako \"*dwa razy więcej*\" niż Queenstown, co jest bezsensem. Porty nie mają żadnej naturalnej kolejności. Dla zmiennych **nominalnych** (bez kolejności) z więcej niż 2 kategoriami potrzebujemy czegoś lepszego."
]
},
{
"cell_type": "markdown",
"id": "53abe06f-931e-48ec-b95e-4f530fadae68",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"`embarked` ma trzy wartości: S (Southampton), C (Cherbourg), Q (Queenstown). Po zakodowaniu LabelEncoderem model \"wierzy\", że np. średnia z C i Q to S. Podaj 2–3 przykłady kolumn **z potencjalnych projektów semestralnych** (gatunki drzew, typ aktywności, kategoria dźwięku...), dla których label encoding byłby bezpieczny (naturalny porządek lub 2 kategorie), i 2–3 przykłady, dla których wprowadziłby fałszywy porządek."
]
},
{
"cell_type": "markdown",
"id": "a81a6b94-7fdd-416a-9fe8-fb25264fb13e",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "5a4d5ddc-997d-4c1a-9624-5b7ea29ce751",
"metadata": {},
"source": [
"### Zadanie 5.2\n",
"#### One-hot encoding: jedna kolumna na kategorię\n",
"\n",
"Standardowe rozwiązanie dla zmiennych nominalnych: każda kategoria dostaje **własną kolumnę 0/1**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5017b43c-7684-4c2d-9251-2f07525dc95b",
"metadata": {},
"outputs": [],
"source": [
"# pd.get_dummies tworzy kolumny binarne dla każdej kategorii\n",
"onehot = pd.get_dummies(titanic[['sex', 'embarked', 'class']], dtype=int)\n",
"print(\"Przed kodowaniem:\", titanic[['sex', 'embarked', 'class']].shape)\n",
"print(\"Po kodowaniu: \", onehot.shape)\n",
"onehot.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d71848a2-0dea-4db7-bfdb-77ac999658d8",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: wymiar \"eksploduje\" wraz z liczbą kategorii\n",
"# Sprawdźmy, co by było z kolumną, która ma DUŻO unikalnych wartości\n",
"onehot_wieku = pd.get_dummies(titanic['age'].round(), dtype=int, prefix='wiek')\n",
"print(f\"Kolumna wiek ma {titanic['age'].nunique()} unikalnych wartości -> \"\n",
" f\"one-hot tworzy {onehot_wieku.shape[1]} kolumn!\")"
]
},
{
"cell_type": "markdown",
"id": "b6d18990-0998-4844-a4ab-ee3d3f3b4d41",
"metadata": {},
"source": [
"**Kiedy one-hot, a kiedy nie?**\n",
"- dobrze działa przy **małej liczbie kategorii** (zasada praktyczna: do kilkunastu),\n",
"- nie wprowadza fałszywego porządku,\n",
"- może tworzyć zjawisko tzw. *przekleństwa wymiarowości* - w przypadku danych typu miasto, kod pocztowy, tytuł filmu, gdyż tworzy setki lub tysiące kolumn."
]
},
{
"cell_type": "markdown",
"id": "b49f5387-7caf-447a-991a-5a9e18faaada",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Ile kolumn powstałoby z one-hot encodingu kolumny `deck` (pokład) na Titanicu? A ile z kolumny `country` w zbiorze 167 państw z ćwiczeń o klastrowaniu? Kiedy eksplodująca liczba kolumn zaczyna przeszkadzać? *(podpowiedź: pomyśl o KNN z Części 2 — odległości w bardzo wysokich wymiarach)*\n"
]
},
{
"cell_type": "markdown",
"id": "d3eeeccb-b093-42ea-bc4c-50ff22e58624",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "0381de32-060e-4980-bf1d-1d5a03d52376",
"metadata": {},
"source": [
"### Zadanie 5.3\n",
"#### Kodowanie porządkowe (ordinal): gdy kolejność ma znaczenie\n",
"\n",
"Czasem kategorie mają swoją naturalną kolejność, wtedy warto je zakodować wprost."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50aac186-90d1-4613-a7d7-72cdc91017f9",
"metadata": {},
"outputs": [],
"source": [
"# Klasa kabiny: Third < Second < First — jest to porządek \"premium\" (i cenowy!)\n",
"porzadek = {'Third': 1, 'Second': 2, 'First': 3}\n",
"titanic['class_ordinal'] = titanic['class'].map(porzadek)\n",
"\n",
"# Porównanie: ile wynosi szansa przeżycia wg klasy? Porządek ma znaczenie merytoryczne\n",
"print(titanic.groupby('class_ordinal', observed=True)['survived'].mean())\n",
"\n",
"titanic[['class', 'class_ordinal', 'survived']].head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af26a572-6021-4aff-85ef-caa8bf95ac44",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: zamień na porządek odwrotny i sprawdź, co się stanie z interpretacją\n",
"odwrotny = {'Third': 3, 'Second': 2, 'First': 1}\n",
"titanic['class_odwrotnie'] = titanic['class'].map(odwrotny)\n",
"print(titanic.groupby('class_odwrotnie', observed=True)['survived'].mean())"
]
},
{
"cell_type": "markdown",
"id": "45db7e52-e1b7-4ffe-9cec-cf8195dd07dd",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Które z poniższych zmiennych zakodowałbyś porządkowo, a które nominalnie (one-hot)? Uzasadnij w jednym zdaniu każdorazowo: (a) poziom wykształcenia, (b) gatunek drzewa, (c) rozmiar koszulki (S/M/L/XL), (d) kolor produktu, (e) etap dojrzałości owocu."
]
},
{
"cell_type": "markdown",
"id": "004486ae-80d6-4015-82a6-2f0fc24b382c",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "1827e50d-242e-455e-a6d0-19f56ea0ff37",
"metadata": {},
"source": [
"### Zadanie 5.4\n",
"#### Frequency encoding: liczba wystąpień zamiast nazwy\n",
"\n",
"Metoda na kolumny o **wielu kategoriach**: zamiast nazwy wstawiamy, **jak często** ta kategoria występuje w danych (udział procentowy)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "83b516fa-8693-4583-9621-350c7a3e4a2e",
"metadata": {},
"outputs": [],
"source": [
"# Udział każdej kategorii w całym zbiorze\n",
"czestosci = titanic['embarked'].value_counts(normalize=True)\n",
"print(\"Częstości kategorii:\\n\", czestosci)\n",
"\n",
"titanic['embarked_freq'] = titanic['embarked'].map(czestosci)\n",
"titanic[['embarked', 'embarked_freq']].head()"
]
},
{
"cell_type": "markdown",
"id": "cdc7a32c-ad0f-4b45-82e9-e32434319dc7",
"metadata": {},
"source": [
"**Kiedy frequency encoding?** Gdy kategorii jest bardzo dużo (miasta, produkty, utwory muzyczne) i one-hot byłby niepraktyczny. Intuicja: rzadkie kategorie dostają małe liczby, częste - duże; model może z tego skorzystać.\n",
"\n",
"Ryzyko: **różne kategorie o identycznej częstości dostają tę samą liczbę** - możemy wtedy stracić zdolność ich rozróżnienia. Częstość to informacja \"na skróty\", nie pełny opis kategorii."
]
},
{
"cell_type": "markdown",
"id": "70006788-6499-415e-b449-b4a02be4979a",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Załóżmy, że pracujemy nad systemem rekomendacji filmów, który ma kolumnę `tytul_filmu`, na którą składa się tysiące unikalnych wartości. Proszę się zastanowić nad trzema wariantami: one-hot (tysiące kolumn), frequency encoding (jedna kolumna), label encoding (jedna kolumna z fałszywym porządkiem). Który wariant można wybrać i jakie informacje będziemy tracić w każdym przypadku?"
]
},
{
"cell_type": "markdown",
"id": "8e014853-72cb-4ef4-b718-e73320f8d88b",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "6520987c-f5e1-4c1c-a037-472f2199390a",
"metadata": {},
"source": [
"### Zadanie 5.5\n",
"#### Inżynieria cech: nowe kolumny z istniejących\n",
"\n",
"Często największy zysk daje **nowa cecha** ukryta w danych: suma, stosunek, flaga logiczna."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "465f3e5b-2ffa-4f1f-b884-1ac852f03057",
"metadata": {},
"outputs": [],
"source": [
"titanic['family_size'] = titanic['sibsp'] + titanic['parch'] + 1\n",
"titanic['is_alone'] = (titanic['family_size'] == 1).astype(int)\n",
"titanic['is_child'] = (titanic['age'] < 18).astype(int)\n",
"titanic['fare_per_person'] = titanic['fare'] / titanic['family_size']\n",
"\n",
"# Walidacja: czy cecha różnicuje cel?\n",
"print(titanic.groupby('family_size', observed=True)['survived'].mean())\n",
"print(titanic.groupby('is_child', observed=True)['survived'].mean())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "271baa01-5935-4cec-8d7a-7b1ad87c2626",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
"sns.barplot(data=titanic, x='family_size', y='survived', ax=axes[0])\n",
"sns.barplot(data=titanic, x='is_child', y='survived', hue='sex', ax=axes[1])\n",
"plt.tight_layout(); plt.show()"
]
},
{
"cell_type": "markdown",
"id": "1ecd2d21-56f9-42c1-8be5-5a2dc0913ab2",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Która nowa cecha najsilniej różnicuje przeżycie? Czy `is_alone` wnosi coś ponad `family_size`, czy jest redundantna?\n",
"Proszę zaproponować **jedną własną cechę** w Waszym projekcie - skąd pomysł i jak ją zwalidować?"
]
},
{
"cell_type": "markdown",
"id": "1a15f4ad-6e4a-4b4a-a147-a0715a135ca6",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "02c6bb3d-46bc-41de-9bfe-130d6773b33f",
"metadata": {},
"source": [
"## Część 6: Redukcja wymiarowości – te same dane w nowym układzie"
]
},
{
"cell_type": "markdown",
"id": "7e61c469-eba5-4a3f-be59-fd18e0a6730f",
"metadata": {},
"source": [
"Co, gdy cech jest bardzo dużo - setki, tysiące? Wizualizacja jest niemożliwa, a wiele cech jest redundantnych. **Redukcja wymiarowości** zamienia wiele cech na mało nowych, „zbiorczych\" zmiennych, zachowując jak najwięcej informacji - to klasyczna **reprezentacja danych**: te same obserwacje, zupełnie inny układ współrzędnych."
]
},
{
"cell_type": "markdown",
"id": "58a58f0c-759b-4379-8c82-9d4bfe43891a",
"metadata": {},
"source": [
"### Zadanie 6.1\n",
"#### PCA: kompresja z zachowaniem wariancji\n",
"\n",
"**PCA (Principal Component Analysis - analiza głównych składowych)** znajduje nowe osie - kombinacje oryginalnych cech tak, by pierwsza tłumaczyła najwięcej wariancji. Celem jests zmniejszenie liczby zmiennych przy minimalnej utracie informacji."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d45336fa-076c-4c59-bac6-dbcc7785b302",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.decomposition import PCA\n",
"\n",
"# PCA jest oparte na wariancji/odległości — najpierw skalowanie!\n",
"X_scaled_iris = StandardScaler().fit_transform(iris[features])\n",
"\n",
"pca_full = PCA(random_state=42).fit(X_scaled_iris)\n",
"wariancja = pd.Series(pca_full.explained_variance_ratio_,\n",
" index=[f'PC{i+1}' for i in range(4)])\n",
"print(wariancja.round(3))\n",
"print(f\"Dwie pierwsze składowe tłumaczą {wariancja.iloc[:2].sum():.1%} wariancji!\")\n",
"\n",
"# Wykres osypiska: ile wymiarów wystarczy?\n",
"plt.figure(figsize=(6, 4))\n",
"plt.plot(range(1, 5), wariancja.cumsum(), marker='o', linestyle='--')\n",
"plt.axhline(0.95, color='r', linestyle=':', label='próg 95%')\n",
"plt.xlabel('Liczba składowych'); plt.ylabel('Skumulowana wariancja')\n",
"plt.title('PCA: ile wymiarów wystarczy?'); plt.legend(); plt.grid(True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "f3991330-0943-40cb-867c-926f8f8307ad",
"metadata": {},
"source": [
"**Jak interpretować?** Szukamy miejsca, gdzie kolejne składowe przestają dawać wyraźny zysk. W Iris prawie cała informacja mieści się w 2 wymiarach mimo 4 cech - dowód na redundancję (skorelowane cechy „sklejają się\" w jedną składową)."
]
},
{
"cell_type": "markdown",
"id": "902802de-ace8-4495-b328-4a1ff0ce870a",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Ile składowych potrzeba na 95% wariancji Iris? Jak to się ma do macierzy korelacji z Zadania 1.3? Gdyby zredukować Iris do 2 wymiarów przed trenowaniem modelu - ile informacji tracimy?"
]
},
{
"cell_type": "markdown",
"id": "38a21b58-b972-4ce3-b1da-9b3667acba11",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "064233fa-0934-4c5f-afa0-e045985d2225",
"metadata": {},
"source": [
"### Zadanie 6.2\n",
"#### Wizualizacja 2D i grupowanie na składowych"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c23ffae7-a38e-4940-88e5-c4ae754f5127",
"metadata": {},
"outputs": [],
"source": [
"# PCA → 2 składowe\n",
"X_pca = PCA(n_components=2, random_state=42).fit_transform(X_scaled_iris)\n",
"\n",
"plt.figure(figsize=(6, 5))\n",
"sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=iris['species'], s=50)\n",
"plt.xlabel(f\"PC1 ({wariancja.iloc[0]:.0%} wariancji)\")\n",
"plt.ylabel(f\"PC2 ({wariancja.iloc[1]:.0%} wariancji)\")\n",
"plt.title('Iris po PCA: 4 cechy → 2 wymiary')\n",
"plt.show()\n",
"\n",
"# EKSPERYMENT: KMeans na 2 składowych — porównaj z Zadaniem 1.5!\n",
"klastry_pca = KMeans(n_clusters=3, random_state=42, n_init=10).fit_predict(X_pca)\n",
"print(pd.crosstab(iris['species'], klastry_pca,\n",
" rownames=['gatunek'], colnames=['klastr na 2 składowych']))"
]
},
{
"cell_type": "markdown",
"id": "df644f48-b3c2-476a-9113-bdc20b36ee24",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"Czy klastry na 2 składowych różnią się od Zadania 1.5 (4 cechy)? Czy PCA coś „zgubił\", czy raczej odsłonił strukturę? Proszę podać sytuację, w której redukcja wymiarów **pomaga**, i taką, w której **szkodzi**."
]
},
{
"cell_type": "markdown",
"id": "79cdb300-42da-4e45-9e9a-05e5427d7730",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "719dd99c-7265-433d-b731-b37803714915",
"metadata": {},
"source": [
"### Zadanie 6.3\n",
"#### t-SNE: nieliniowa mapa sąsiedztw\n",
"\n",
"**t-SNE** buduje mapę 2D, na której podobne obserwacje leżą blisko, różne - daleko. Często ładniej rozdziela grupy, ale jest **wolniejszy**, trudny do interpretacji i **wrażliwy na parametr `perplexity`**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7185c1f4-a01a-457b-b3a4-5ecf2359db2e",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.manifold import TSNE\n",
"\n",
"X_tsne = TSNE(n_components=2, random_state=42, perplexity=30)\\\n",
" .fit_transform(X_scaled_iris)\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n",
"sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=iris['species'], s=40, ax=axes[0])\n",
"axes[0].set_title('PCA (liniowe)')\n",
"sns.scatterplot(x=X_tsne[:, 0], y=X_tsne[:, 1], hue=iris['species'], s=40, ax=axes[1])\n",
"axes[1].set_title('t-SNE (nieliniowe)')\n",
"plt.suptitle('Te same dane — dwie mapy 2D'); plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aeadb897-3510-4e35-a19f-c98745ab3ee4",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: wpływ perplexity na mapę t-SNE\n",
"fig, axes = plt.subplots(1, 4, figsize=(18, 4))\n",
"for ax, perp in zip(axes, [2, 10, 30, 100]):\n",
" X_t = TSNE(n_components=2, random_state=42, perplexity=perp)\\\n",
" .fit_transform(X_scaled_iris)\n",
" sns.scatterplot(x=X_t[:, 0], y=X_t[:, 1], hue=iris['species'],\n",
" s=20, legend=False, ax=ax)\n",
" ax.set_title(f'perplexity = {perp}')\n",
"plt.suptitle('t-SNE: zmiana parametru to inna mapa!'); plt.show()"
]
},
{
"cell_type": "markdown",
"id": "daf99248-5354-4562-b348-a64f1f52ee88",
"metadata": {},
"source": [
"**PCA czy t-SNE?** \n",
"\n",
"PCA: liniowe, interpretowalne składowe, szybkie, można rzutować nowe dane (transform) -> **preprocessing do modeli**. \n",
"\n",
"t-SNE: nieliniowe, tylko mapa bez interpretacji osi, wolne, brak transformu -> **wizualizacja i eksploracja**.\n",
"Odległości i rozmiary „klastrów\" na mapie t-SNE należy traktować ostrożnie - służy do patrzenia, nie do zasilania modelu."
]
},
{
"cell_type": "markdown",
"id": "687b3563-6776-459e-aa24-83207ce2b5f8",
"metadata": {},
"source": [
"## Część 7: Szeregi czasowe - dane w czasie"
]
},
{
"cell_type": "markdown",
"id": "b64c3ed1-d31e-432f-8196-001bf9d9748a",
"metadata": {},
"source": [
"Dotąd obserwacje były **niezależne**. W wielu projektach (aktywność fizyczna, czas dojazdu, dźwięki) dane mają naturalny **porządek czasowy** - dzisiejszy pomiar wiąże się z wczorajszym. To zmienia analizę i otwiera nowe możliwości inżynierii cech.\n",
"\n",
"Symulujemy **czas dojazdu do uczelni mierzony przez 20 tygodni** - bardzo bliski realnym projektom."
]
},
{
"cell_type": "markdown",
"id": "782cabca-5421-4746-9567-0bf6520a9022",
"metadata": {},
"source": [
"### Zadanie 7.1 \n",
"#### Wizualizacja szeregu czasowego"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4e75cc1a-bdc0-41cb-a0fe-c0335674390c",
"metadata": {},
"outputs": [],
"source": [
"rng = np.random.default_rng(21)\n",
"n_dni = 100 # 20 tygodni × 5 dni roboczych\n",
"daty = pd.date_range('2026-03-02', periods=n_dni, freq='B')\n",
"\n",
"# składowe: delikatny trend (sezon remontowy) + cykl tygodniowy + szum + „awaria\"\n",
"trend = np.linspace(26, 30, n_dni) # powolne wydłużanie dojazdu\n",
"cykl_tygodniowy = np.tile([1.0, 3.5, 4.0, 2.0, -2.0], 20) # pon–pt (szczyt w śrd)\n",
"szum = rng.normal(0, 2.0, n_dni)\n",
"awaria = np.zeros(n_dni); awaria[62:65] = 18 # 3 dni blokad (np. wypadek)\n",
"\n",
"dojazdy = pd.DataFrame({'czas': trend + cykl_tygodniowy + szum + awaria},\n",
" index=daty)\n",
"\n",
"dojazdy.plot(figsize=(12, 4), legend=False, marker='.', alpha=0.5)\n",
"plt.title('Czas dojazdu do uczelni [min]')\n",
"plt.xlabel('data'); plt.ylabel('minuty')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "3d5d917a-30db-4df1-92c2-1ac04e6ef3ea",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Czy na wykresie widać cykl tygodniowy? Delikatny trend? Nagły „skok\" (np. wywołany wypadkiem, awarią, korkiem)? Które z tych trzech zjawisk widać „gołym okiem\", a które dopiero po chwili patrzenia? Dlaczego samo `describe()` nie wykryłoby żadnego zjawiska? *(podpowiedź: statystyki opisowe ignorują kolejność wierszy)*"
]
},
{
"cell_type": "markdown",
"id": "406cbf9d-54a7-4255-a93b-009a5497831a",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "314f2b52-4488-4886-a4c4-f4187ab484ca",
"metadata": {},
"source": [
"### Zadanie 7.2\n",
"#### Wygładzanie: średnia krocząca"
]
},
{
"cell_type": "markdown",
"id": "5e49d9ea-9c20-4825-9bee-2731b900c800",
"metadata": {},
"source": [
"Szum utrudnia dostrzeżenie wzorca. **Średnia krocząca** (moving average) uśrednia okno N kolejnych pomiarów - szum się znosi, wzorzec zostaje."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8f5818e-5b08-4427-83d4-ad6695714668",
"metadata": {},
"outputs": [],
"source": [
"# EKSPERYMENT: zmień rozmiar okna (5, 10, 20) i obserwuj efekt wygładzania\n",
"okno = 5\n",
"dojazdy['srednia_kroczaca'] = dojazdy['czas'].rolling(window=okno).mean()\n",
"\n",
"dojazdy[['czas', 'srednia_kroczaca']].plot(figsize=(12, 4), alpha=0.6)\n",
"plt.title(f'Wygładzanie: średnia krocząca (okno = {okno} dni)')\n",
"plt.xlabel('data'); plt.ylabel('minuty')\n",
"plt.show()\n",
"\n",
"# dodatkowo: odchylenie standardowe w oknie — miara niestabilności dojazdu\n",
"dojazdy['zmienosc'] = dojazdy['czas'].rolling(window=okno).std()\n",
"dojazdy[['zmienosc']].plot(figsize=(12, 3), color='tab:orange')\n",
"plt.title('Zmienność dojazdu (std w oknie) — kiedy dojazd był najmniej przewidywalny?')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "56dfdae3-e2c8-4f1a-8e43-410e598215a6",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Jak zmienił się wykres po wygładzeniu? Co tracimy, a co zyskujemy przy większym oknie (np. 20)? Kiedy zmienność była największa i z czym mogła się wiązać? Dla projektu z aktywnością fizyczną: co mogłaby oznaczać wysoka zmienność przyspieszenia w oknie?"
]
},
{
"cell_type": "markdown",
"id": "70baab11-8550-4202-96f5-8a0362024720",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "014b9501-37e7-4c56-9a12-841bf7e4f827",
"metadata": {},
"source": [
"### Zadanie 7.3 \n",
"#### Ekstrakcja cech"
]
},
{
"cell_type": "markdown",
"id": "760e0aa7-8320-464d-a6c3-d4c49e729427",
"metadata": {},
"source": [
"Dla każdego pomiaru możemy wyciągnąć informacje z jego daty: dzień tygodnia, numer tygodnia, „czy przed/po południu\". To klasyczna inżynieria cech dla szeregów czasowych."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0aa9b15c-bae3-4755-97f1-981a089ab560",
"metadata": {},
"outputs": [],
"source": [
"dojazdy['dzien_tygodnia'] = dojazdy.index.dayofweek # 0=pon, 4=pt\n",
"dojazdy['nazwa_dnia'] = dojazdy.index.day_name()\n",
"\n",
"# Średni czas dojazdu wg dnia tygodnia\n",
"srednie_dni = dojazdy.groupby('nazwa_dnia', observed=True)['czas'].mean()\n",
"print(srednie_dni)\n",
"\n",
"kolejnosc = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']\n",
"srednie_dni.reindex(kolejnosc).plot(kind='bar', figsize=(8, 4), color='tab:green')\n",
"plt.title('Średni czas dojazdu wg dnia tygodnia')\n",
"plt.ylabel('minuty'); plt.xticks(rotation=15)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "fe75d225-6130-4bcb-a743-f4e5c578863c",
"metadata": {},
"source": [
"**REFLEKSJA:**\n",
"\n",
"Który dzień tygodnia ma najdłuższy dojazd - zgadza się to z cyklem z Zadania 7.1? Proszę podać przykłady cech dla innych projektów: aktywność fizyczna (dzień/godzina/wakacje?), dźwięki otoczenia (pora dnia vs weekend), czas przejazdu, itp."
]
},
{
"cell_type": "markdown",
"id": "c23aa731-22b7-48ff-b6ef-e86637a72ec3",
"metadata": {},
"source": [
"*(tutaj wpisz odpowiedź)*\n",
"\n",
"*_______________________________________*"
]
},
{
"cell_type": "markdown",
"id": "f1e5467e-47ca-433c-9799-722ca5af1dc2",
"metadata": {},
"source": [
"# Podsumowanie - co wykorzystać do projektu?"
]
},
{
"cell_type": "markdown",
"id": "9145138d-2404-4a88-b128-8f660b94c920",
"metadata": {},
"source": [
"Powyższe ćwiczenia miały jeden cel: zrozumienie, że praca z danymi **poprzedza i determinuje** uczenie maszynowe. Konkretnie:\n",
"\n",
"1. **EDA przed modelowaniem.** Jaki jest rozmiar i typy danych, braki, rozkłady, outliery i korelacje. Histogramy i boxploty powiedzą więcej niż dziesięć stron dokumentacji.\n",
"3. **Czyszczenie to decyzje, nie procedura.** Usunąć wiersz czy uzupełnić medianą? Wyciąć outliera czy go zostawić, bo to rzadki, ale prawdziwy przypadek? Każda decyzja zmienia dane, a więc i model - musi więc być świadoma i uzasadniona.\n",
"4. **Skalowanie ma znaczenie.** \n",
"5. **Obraz to macierz.** Zdjęcie, film, zapis z mikrofonu, dane z ankiety - wszystko trafia do modelu jako liczby. Projektując cechy (nawet tak proste jak średnia jasność), decydujemy o tym, co model \"zobaczy\".\n",
"6. **Mało danych? Augmentuj - ale mądrze.** Obrót czy zmiana jasności bywają bezpłatnymi przykładami treningowymi, ale niektóre transformacje niszczą sens etykiety."
]
},
{
"cell_type": "markdown",
"id": "079c514c-9ce8-440e-95e8-53e2c3dfc1cb",
"metadata": {},
"source": [
"# Pytania kontrolne \n",
"\n",
"1. **Typ danych:** tabele / obrazy / dźwięk / szeregi czasowe? Jaka będzie struktura jednego przykładu (kolumny, rozmiar macierzy, długość nagrania)?\n",
"2. **Źródła braków i szumu:** co może pójść \"*nie tak*\" przy zbieraniu danych wybraną metodą (telefon, różne osoby, różne oświetlenie, różne urządzenia)? Jakie braki i jakich outlierów można się spodziewać?\n",
"3. **Etykiety:** czy etykietujecie ręcznie? Jak zabezpieczycie się przed błędami etykietowania?\n",
"4. **Skala zbioru:** ile przykładów na klasę planujecie zebrać? Czy będzie potrzebna augmentacja? Jeśli tak, to jakie konkretnie transformacje są bezpieczne dla Waszego problemu?\n",
"5. **Pierwsza hipoteza:** na podstawie dzisiejszych ćwiczeń, jakie 2 - 3 wizualizacje można wykonać na Waszych danych i co chcecie dzięki nim sprawdzić?"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.16"
}
},
"nbformat": 4,
"nbformat_minor": 5
}