Build with AI Bootcamp
Od nule do funkcionalne AI web aplikacije, klasifikacija modnih proizvoda pomoću konvolucionih neuronskih mreža (CNN).
Uvod i podešavanje okruženja
Počinjemo sa podešavanjem Google Colab-a i uključivanjem GPU-a. GPU je neophodan jer ubrzava treniranje modela 10-100x u odnosu na CPU.
Uključite GPU
- U Google Colab-u idite na Runtime → Change runtime type
- U Hardware accelerator izaberite GPU (T4 je besplatan)
- Kliknite Save
Provera GPU-a
# Provera da li je GPU dostupan
import tensorflow as tf
print("TensorFlow verzija:", tf.__version__)
print("="*50)
gpus = tf.config.list_physical_devices('GPU')
if gpus:
print(f"GPU je dostupan! Pronađeno {len(gpus)} GPU uređaj(a):")
for gpu in gpus:
print(f" - {gpu.name}")
else:
print("UPOZORENJE: GPU nije pronađen!")
print("Idite na Runtime → Change runtime type → GPU")
Instalacija i import biblioteka
# Instalacija
!pip install gradio gdown -q
# Importi
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os, zipfile, gdown
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
from PIL import Image
import warnings
warnings.filterwarnings('ignore')
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['figure.figsize'] = [12, 8]
plt.rcParams['font.size'] = 12
print("Sve biblioteke su uspešno učitane!")
Preuzimanje i učitavanje dataseta
Koristimo Fashion Product Images, realne fotografije modnih proizvoda. Radimo sa 4 klase: T-shirt, Jeans, Sneakers, Jacket.
Preuzimanje
FILE_ID = '1YpceXZvh-8idz-nKDJjUh3-nrlVvhyYY'
print("Preuzimanje Fashion dataseta...")
url = f'https://drive.google.com/uc?id={FILE_ID}'
output = 'fashion_dataset.zip'
gdown.download(url, output, quiet=False)
with zipfile.ZipFile(output, 'r') as zip_ref:
zip_ref.extractall('.')
os.remove(output)
print("Dataset uspešno preuzet i raspakovan!")
Pronalaženje i učitavanje
def pronadji_dataset():
for folder in ['fashion_subset gdg', 'fashion_subset', 'fashion_dataset']:
if os.path.exists(folder):
if os.path.exists(os.path.join(folder, 'styles.csv')):
return folder
for item in os.listdir('.'):
if os.path.isdir(item) and os.path.exists(os.path.join(item, 'styles.csv')):
return item
return None
DATASET_PATH = pronadji_dataset()
STYLES_PATH = os.path.join(DATASET_PATH, 'styles.csv')
IMAGES_PATH = os.path.join(DATASET_PATH, 'images')
df = pd.read_csv(STYLES_PATH, on_bad_lines='skip')
print(f"Učitano {len(df):,} proizvoda")
Izbor klasa i učitavanje slika
ODABRANE_KATEGORIJE = {
'Tshirts': 'T-shirt', 'Jeans': 'Jeans',
'Casual Shoes': 'Sneakers', 'Jackets': 'Jacket'
}
df_filtered = df[df['articleType'].isin(ODABRANE_KATEGORIJE.keys())].copy()
df_filtered['class_name'] = df_filtered['articleType'].map(ODABRANE_KATEGORIJE)
IMENA_KLASA = ['T-shirt', 'Jeans', 'Sneakers', 'Jacket']
BROJ_KLASA = len(IMENA_KLASA)
label_map = {name: idx for idx, name in enumerate(IMENA_KLASA)}
df_filtered['label'] = df_filtered['class_name'].map(label_map)
IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS = 80, 60, 3
def ucitaj_sliku(image_id, images_dir, target_size=(IMG_HEIGHT, IMG_WIDTH)):
img_path = os.path.join(images_dir, f"{image_id}.jpg")
if not os.path.exists(img_path): return None
try:
img = Image.open(img_path).convert('RGB')
img = img.resize((target_size[1], target_size[0]))
return np.array(img)
except: return None
slike, labele = [], []
for klasa_idx, klasa_ime in enumerate(IMENA_KLASA):
df_klasa = df_filtered[df_filtered['class_name'] == klasa_ime].head(1500)
ucitano = 0
for _, row in df_klasa.iterrows():
img = ucitaj_sliku(row['id'], IMAGES_PATH)
if img is not None:
slike.append(img); labele.append(klasa_idx); ucitano += 1
print(f" {klasa_ime}: {ucitano} slika")
X = np.array(slike)
y = np.array(labele)
print(f"UKUPNO: {len(X):,} slika | Dimenzije: {X.shape}")
Preprocesiranje i podela podataka
Pre treniranja moramo da normalizujemo slike i podelimo podatke na trening, validacioni i test set.
Pikseli imaju vrednosti 0-255. Neuronske mreže bolje rade sa malim brojevima (0.0 - 1.0). Kojim brojem treba podeliti?
print("Pre normalizacije:")
print(f" Min: {X.min()}, Max: {X.max()}")
X = X.astype('float32') / _____ # <-- DOPUNITE: kojim brojem delimo?
print("Posle normalizacije:")
print(f" Min: {X.min():.2f}, Max: {X.max():.2f}")
Delimo podatke: 70% trening, 15% validacija, 15% test. Koristimo train_test_split iz sklearn.
# Korak 1: Izdvajamo test set (15%)
X_temp, X_test, y_temp, y_test = train_test_split(
X, y,
test_size=_____, # <-- DOPUNITE: koliki procenat za test?
random_state=42,
stratify=_____ # <-- DOPUNITE: po čemu stratifikujemo?
)
# Korak 2: Od ostatka izdvajamo validation set
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp,
test_size=0.176,
random_state=42,
stratify=y_temp
)
print(f"Trening: {len(X_train):,} | Validation: {len(X_val):,} | Test: {len(X_test):,}")
test_size=0.15 znači 15%. stratify=y čuva proporcije klasa u svakom setu.Vizualizacija distribucije
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
colors = plt.cm.Set2(np.linspace(0, 1, BROJ_KLASA))
for ax, (data, name) in zip(axes, [(y_train, 'Trening'), (y_val, 'Validation'), (y_test, 'Test')]):
unique, counts = np.unique(data, return_counts=True)
bars = ax.bar([IMENA_KLASA[i] for i in unique], counts, color=colors)
ax.set_title(f'{name} set', fontweight='bold')
for bar, count in zip(bars, counts):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5, str(count), ha='center')
plt.tight_layout()
plt.show()
CNN arhitektura
CNN (Convolutional Neural Network) je tip neuronske mreže specijalizovan za slike. Naš model ima 3 konvoluciona bloka sa rastućim brojem filtera: 32 → 64 → 128.
| Sloj | Funkcija |
|---|---|
| Conv2D | Detektuje obrasce (ivice, teksture, oblike) |
| BatchNormalization | Stabilizuje i ubrzava treniranje |
| MaxPooling2D | Smanjuje dimenzije, zadržava bitne informacije |
| Dropout | Sprečava overfitting (nasumično gasi neurone) |
| Dense | Potpuno povezan sloj za klasifikaciju |
Najvažniji deo radionice! Popunite praznine u prvom bloku i poslednjem sloju. Blokovi 2 i 3 su dati kao primer.
INPUT_SHAPE = (IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)
def kreiraj_cnn_model():
model = models.Sequential([
# ===== BLOK 1 =====
layers.Conv2D(_____, (_____, _____), activation='_____', padding='same', input_shape=INPUT_SHAPE),
layers.BatchNormalization(),
layers.Conv2D(32, (3, 3), activation='relu', padding='same'),
layers.MaxPooling2D((_____, _____)), # <-- dimenzije pooling-a
layers.Dropout(_____), # <-- procenat dropout-a
# ===== BLOK 2 (64 filtera) =====
layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# ===== BLOK 3 (128 filtera) =====
layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
layers.BatchNormalization(),
layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# ===== KLASIFIKACIJA =====
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.5),
layers.Dense(128, activation='relu'),
layers.Dropout(0.5),
layers.Dense(_____, activation='_____') # <-- koliko klasa? koji activation?
])
return model
model = kreiraj_cnn_model()
model.summary()
Kompilacija i treniranje
Kompilacija govori modelu kako da uči: kojim algoritmom, kako da meri grešku, i šta da prati.
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=_____), # <-- brzina učenja
loss='_____', # <-- loss funkcija
metrics=['_____'] # <-- šta pratimo?
)
print("Model kompajliran!")
learning_rate=0.001 | loss='sparse_categorical_crossentropy' | metrics=['accuracy']Callbacks
callbacks = [
keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6, verbose=1),
keras.callbacks.EarlyStopping(monitor='val_loss', patience=7, restore_best_weights=True, verbose=1)
]
Odaberite broj epoha i veličinu batch-a. EarlyStopping će zaustaviti treniranje ako model prestane da se poboljšava.
EPOCHS = _____ # <-- broj epoha (preporuka: 25)
BATCH_SIZE = _____ # <-- veličina batch-a (preporuka: 32)
history = model.fit(
X_train, y_train,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_data=(X_val, y_val),
callbacks=callbacks,
verbose=1
)
Vizualizacija treniranja
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(history.history['loss'], label='Trening', linewidth=2)
axes[0].plot(history.history['val_loss'], label='Validation', linewidth=2)
axes[0].set_title('Loss', fontweight='bold')
axes[0].legend()
axes[1].plot(history.history['accuracy'], label='Trening', linewidth=2)
axes[1].plot(history.history['val_accuracy'], label='Validation', linewidth=2)
axes[1].set_title('Accuracy', fontweight='bold')
axes[1].legend()
plt.tight_layout()
plt.show()
Evaluacija modela
Vreme je da vidimo koliko je naš model dobar na podacima koje nikada nije video, na test setu.
test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0)
print(f"Test Accuracy: {test_accuracy:.2%}")
y_pred_probs = model.predict(X_test, verbose=0)
y_pred = np.argmax(y_pred_probs, axis=1)
# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=IMENA_KLASA, yticklabels=IMENA_KLASA, annot_kws={'size': 14})
plt.title('Confusion Matrix', fontweight='bold')
plt.xlabel('Prediktovana klasa')
plt.ylabel('Stvarna klasa')
plt.show()
print(classification_report(y_test, y_pred, target_names=IMENA_KLASA))
Ograničenja AI - model ne može da kaže "Ne znam"
Šta se desi kad modelu damo sliku nečega što nikada nije video? Hajde da testiramo sa besmislenim slikama.
nepoznate = {
'Šum': np.random.rand(IMG_HEIGHT, IMG_WIDTH, 3).astype('float32'),
'Crna': np.zeros((IMG_HEIGHT, IMG_WIDTH, 3), dtype='float32'),
'Bela': np.ones((IMG_HEIGHT, IMG_WIDTH, 3), dtype='float32'),
'Plava': np.concatenate([np.zeros((IMG_HEIGHT, IMG_WIDTH, 2)),
np.ones((IMG_HEIGHT, IMG_WIDTH, 1))], axis=2).astype('float32'),
}
fig, axes = plt.subplots(1, 4, figsize=(14, 4))
fig.suptitle('Model daje predikciju čak i za besmislene slike!', color='red', fontweight='bold')
for ax, (naziv, slika) in zip(axes, nepoznate.items()):
pred = model.predict(slika.reshape(1, IMG_HEIGHT, IMG_WIDTH, 3), verbose=0)[0]
ax.imshow(slika)
ax.set_title(f'{naziv}\n→ {IMENA_KLASA[np.argmax(pred)]} ({pred.max()*100:.0f}%)', color='darkred')
ax.axis('off')
plt.tight_layout()
plt.show()
Data Augmentation
Augmentacija veštački kreira nove slike od postojećih: rotira, pomera, zumira, flipuje. Model vidi "nove" slike u svakoj epohi, što pomaže generalizaciji.
Popunite parametre. Pazite, previše agresivna augmentacija može da pogorša rezultate!
datagen = ImageDataGenerator(
rotation_range=_____, # <-- stepeni rotacije (preporuka: 10)
width_shift_range=_____, # <-- horizontalno pomeranje (preporuka: 0.1)
height_shift_range=_____, # <-- vertikalno pomeranje (preporuka: 0.1)
zoom_range=_____, # <-- zoom (preporuka: 0.1)
horizontal_flip=_____, # <-- True ili False?
fill_mode='nearest'
)
Treniranje sa augmentacijom i poređenje
model_aug = kreiraj_cnn_model()
model_aug.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
datagen.fit(X_train)
history_aug = model_aug.fit(
datagen.flow(X_train, y_train, batch_size=BATCH_SIZE),
epochs=20,
validation_data=(X_val, y_val),
steps_per_epoch=len(X_train) // BATCH_SIZE,
callbacks=callbacks, verbose=1
)
# Poređenje
_, test_acc_orig = model.evaluate(X_test, y_test, verbose=0)
_, test_acc_aug = model_aug.evaluate(X_test, y_test, verbose=0)
print(f"Bez augmentacije: {test_acc_orig:.2%}")
print(f"Sa augmentacijom: {test_acc_aug:.2%}")
best_model = model_aug if test_acc_aug >= test_acc_orig else model
best_model.save('fashion_classifier.keras')
Deploy sa Gradio
Poslednji korak, pravimo web aplikaciju! Gradio omogućava kreiranje interfejsa za ML modele sa svega nekoliko linija koda.
Funkcija prima sliku, resize-uje je, normalizuje, i prosleđuje modelu. Popunite dimenzije i normalizaciju.
import gradio as gr
def klasifikuj(slika):
if isinstance(slika, Image.Image):
slika = np.array(slika)
if len(slika.shape) == 2:
slika = np.stack([slika]*3, axis=-1)
elif slika.shape[2] == 4:
slika = slika[:,:,:3]
slika_pil = Image.fromarray(slika.astype('uint8')).convert('RGB')
slika_resized = slika_pil.resize((_____, _____)) # <-- (IMG_WIDTH, IMG_HEIGHT)
slika_norm = np.array(slika_resized).astype('float32') / _____ # <-- normalizacija
slika_input = slika_norm.reshape(1, IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)
pred = best_model.predict(slika_input, verbose=0)[0]
return {IMENA_KLASA[i]: float(pred[i]) for i in range(BROJ_KLASA)}
Pokretanje aplikacije
# Priprema primera
primeri = []
for klasa in range(BROJ_KLASA):
idx = np.where(y_test == klasa)[0][0]
slika = (X_test[idx] * 255).astype('uint8')
path = f'primer_{IMENA_KLASA[klasa]}.png'
Image.fromarray(slika).save(path)
primeri.append(path)
# Gradio interfejs
demo = gr.Interface(
fn=klasifikuj,
inputs=gr.Image(label="Učitaj sliku"),
outputs=gr.Label(num_top_classes=4, label="Predikcija"),
title="Fashion Classifier",
description="GDG Belgrade & VTŠ Apps Team - Build with AI Bootcamp",
examples=primeri,
theme=gr.themes.Soft()
)
demo.launch(share=True)
Šta smo naučili i kuda dalje?
Danas smo prošli
- AI/ML/DL pojmovi - epoch, batch, loss, accuracy
- Realan dataset - Fashion Product Images (3,866 slika)
- CNN arhitektura - Conv2D, BatchNorm, Pooling, Dropout
- Evaluacija - Confusion Matrix, Classification Report
- Ograničenja AI - model ne može da kaže "ne znam"
- Data Augmentation - rotacija, pomeranje, zoom, flip
- Deploy - Gradio web aplikacija
Prezentacija
Preuzmite slajdove sa radionice
Google Cloud krediti
Besplatni krediti za Google Cloud platformu
Resursi za dalje učenje
| Resurs | Opis | Link |
|---|---|---|
| TensorFlow | Zvanična dokumentacija | tensorflow.org |
| Keras | API za neuronske mreže | keras.io |
| Google ML Crash Course | Besplatan kurs od Google-a | ML Crash Course |
| Gradio | ML web aplikacije | gradio.app |
| Kaggle | Dataseti i takmičenja | kaggle.com |
| fast.ai | Besplatan DL kurs | fast.ai |