KacperM33 commited on
Commit
0f83c3b
·
0 Parent(s):

Initial commit

Browse files
.dockerignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cache Pythona
2
+ __pycache__/
3
+ *.pyc
4
+
5
+ # środowisko badawcze
6
+ *.ipynb
7
+ *.pth
8
+ .git/
9
+ .gitignore
10
+
11
+ # dataset
12
+ food_dataset/
13
+
14
+ # Pliki konfiguracyjne edytora
15
+ .vscode/
16
+ .idea/
.gitattributes ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ *.onnx filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
3
+ *.jpg filter=lfs diff=lfs merge=lfs -text
4
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
5
+ *.pth filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cache Pythona
2
+ __pycache__/
3
+ *.pyc
4
+
5
+ # dataset
6
+ food_dataset/
7
+
8
+ # Pliki konfiguracyjne edytora
9
+ .vscode/
10
+ .idea/
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Bierzemy Linuxa z gotowym Pythonem 3.10
2
+ FROM python:3.10-bullseye
3
+
4
+ # 2. Tworzymy folder /app wewnątrz kontenera i tam wchodzimy
5
+ WORKDIR /app
6
+
7
+ # 3. Kopiujemy TYLKO plik z listą bibliotek
8
+ COPY requirements.txt .
9
+
10
+ # 4. Instalujemy biblioteki (Docker to zapamięta w cache)
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # 5. Kopiujemy całą resztę naszych plików (main.py, index.html, static, modele ONNX)
14
+ COPY . .
15
+
16
+ # 6. Mówimy Dockerowi, że nasza aplikacja będzie gadać przez port 7860
17
+ EXPOSE 7860
18
+
19
+ # 7. Komenda, która odpali się SAMOCZYNNIE po uruchomieniu kontenera
20
+ # Zwróć uwagę na "0.0.0.0" - to wymóg Dockera, żeby wypuścić sygnał na zewnątrz kontenera!
21
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Testy inferencji modeli
2
+
3
+ Testy zostały przeprowadzone z inferencją na CPU.
4
+
5
+ | Model | Błąd MAE [KCAL] | Błąd MAE [FAT] | Błąd MAE [CARB] | Błąd MAE [PROTEIN] | Średni czas Inferencji |
6
+ | :--- | :--- | :--- | :--- | :--- | :--- |
7
+ | **PyTorch** | 63.28 | 3.878 | 8.389 | 5.317 | 40.70 ms |
8
+ | **ONNX** | 63.28 | 3.878 | 8.389 | 5.317 | **6.65 ms** |
9
+ | **ONNX INT8** | 92.687 | 5.910 | 10.404 | 7.359 | 41.38 ms |
10
+
11
+ > Najlepszy model ONNX FP32 (bez kwantyzacji), najkrótszy czas inferencji + najlepsze rezultaty, model ten zostanie użyty w API projektu
12
+
13
+ ### Uruchomienie projektu (Docker)
14
+
15
+ 1. Zbudowanie obrazku Dockera:
16
+
17
+ `docker build -t food-macro-app .`
18
+
19
+ 2. Uruchomienie kontenera:
20
+
21
+ `docker run -p 7860:7860 food-macro-app`
22
+
23
+ 3. W przeglądarce przejść na adres:
24
+
25
+ `localhost:7860`
index.html ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Food Macro | Panel MLOps</title>
7
+ <link rel="icon" type="image/jpeg" href="/static/logo.png">
8
+ <link rel="stylesheet" href="/static/style.css">
9
+ </head>
10
+ <body>
11
+
12
+ <button class="toggle-btn" id="btnTest" onclick="openSidebar('test')">🗂️ Dataset</button>
13
+ <button class="toggle-btn" id="btnExternal" onclick="openSidebar('external')">🌍 Spoza Datasetu</button>
14
+ <button class="toggle-btn" id="btnUser" onclick="openSidebar('user')">👤 Użytkownik</button>
15
+
16
+ <div class="main-panel" id="mainPanel">
17
+ <div class="container">
18
+ <h1>AI Food Macro</h1>
19
+ <p id="modeIndicator" class="mode-indicator">Tryb: Nowe zdjęcie</p>
20
+
21
+ <label class="custom-file-upload">
22
+ <input type="file" id="imageInput" accept="image/*" onchange="previewNewImage(event)">
23
+ 📸 Kliknij, aby wgrać własne zdjęcie
24
+ </label>
25
+
26
+ <img id="preview" src="" alt="Podgląd zdjęcia">
27
+
28
+ <button id="analyzeBtn" onclick="analyzeImage()" disabled>Analizuj Makro</button>
29
+
30
+ <div class="results" id="resultsPanel">
31
+ <table>
32
+ <thead>
33
+ <tr>
34
+ <th class="emoji-col"></th>
35
+ <th>Makro</th>
36
+ <th>Model</th>
37
+ <th class="truth-col">Prawda</th>
38
+ <th class="truth-col">Błąd</th>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr>
43
+ <td class="emoji-col">🔥</td>
44
+ <td>Kalorie</td>
45
+ <td class="val-model" id="res-kcal">0 kcal</td>
46
+ <td class="val-truth truth-col" id="truth-kcal">-</td>
47
+ <td class="val-diff truth-col" id="diff-kcal">-</td>
48
+ </tr>
49
+ <tr>
50
+ <td class="emoji-col">🍗</td>
51
+ <td>Białko</td>
52
+ <td class="val-model" id="res-protein">0 g</td>
53
+ <td class="val-truth truth-col" id="truth-protein">-</td>
54
+ <td class="val-diff truth-col" id="diff-protein">-</td>
55
+ </tr>
56
+ <tr>
57
+ <td class="emoji-col">🥑</td>
58
+ <td>Tłuszcz</td>
59
+ <td class="val-model" id="res-fat">0 g</td>
60
+ <td class="val-truth truth-col" id="truth-fat">-</td>
61
+ <td class="val-diff truth-col" id="diff-fat">-</td>
62
+ </tr>
63
+ <tr>
64
+ <td class="emoji-col">🍞</td>
65
+ <td>Węgle</td>
66
+ <td class="val-model" id="res-carb">0 g</td>
67
+ <td class="val-truth truth-col" id="truth-carb">-</td>
68
+ <td class="val-diff truth-col" id="diff-carb">-</td>
69
+ </tr>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="sidebar" id="sidebar">
77
+ <h2 id="sidebarTitle">Panel</h2>
78
+ <p id="sidebarDesc"></p>
79
+
80
+ <div id="mealsList"></div>
81
+
82
+ <div class="add-meal-form" id="addMealForm" style="display: none;">
83
+ <h3>Dodaj do bazy</h3>
84
+ <input type="text" id="newMealName" placeholder="Nazwa (np. Płatki)">
85
+ <label class="form-label">Zdjęcie Ground Truth:</label>
86
+ <input type="file" id="newMealImage" class="form-file">
87
+
88
+ <div class="form-row">
89
+ <input type="number" id="newMealKcal" placeholder="Kcal">
90
+ <input type="number" id="newMealP" placeholder="Białko(g)">
91
+ </div>
92
+ <div class="form-row">
93
+ <input type="number" id="newMealF" placeholder="Tłuszcz(g)">
94
+ <input type="number" id="newMealC" placeholder="Węgle(g)">
95
+ </div>
96
+ <button onclick="addUserMeal()" class="btn-save">➕ Zapisz</button>
97
+ </div>
98
+ </div>
99
+
100
+ <script src="/static/script.js"></script>
101
+ </body>
102
+ </html>
main.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File
2
+ from fastapi.responses import FileResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ import onnxruntime as ort
5
+ import numpy as np
6
+ from PIL import Image
7
+ import io
8
+ import os
9
+
10
+ # inicjalizacja aplikacji webowej
11
+ app = FastAPI(title="AI Food Macro API", version="1.0")
12
+
13
+ os.makedirs("models", exist_ok=True)
14
+ os.makedirs("static", exist_ok=True)
15
+ os.makedirs("test_images", exist_ok=True)
16
+
17
+ app.mount("/models", StaticFiles(directory="models"), name="models")
18
+ app.mount("/static", StaticFiles(directory="static"), name="static")
19
+ app.mount("/test_images", StaticFiles(directory="test_images"), name="test_images")
20
+
21
+ # załadowanie modelu na start serwera
22
+ print("Ładowanie silnika ONNX na CPU...")
23
+ session = ort.InferenceSession("models/onnx_model.onnx", providers=["CPUExecutionProvider"])
24
+ input_name = session.get_inputs()[0].name
25
+
26
+ # maksyma wyciągnięte ze zbioru treningowego (max_factors)
27
+ # Maksima -> Kcal: 3943.325195, Tłuszcz: 106.343002, Węgle: 844.568604, Białko: 143.492493
28
+ MAX_FACTORS = np.array([3943.325195, 106.343002, 844.568604, 143.492493])
29
+
30
+ def preprocess_image(image: Image.Image) -> np.ndarray:
31
+ """
32
+ Ręczna transformacja zdjęcia (zastępuje transform.Compose z Pytorcha)
33
+ """
34
+ img = image.convert('RGB').resize((224, 224), Image.Resampling.LANCZOS)
35
+
36
+ img_data = np.array(img).astype(np.float32) / 255.0
37
+
38
+ # standaryzacja ImageNet
39
+ mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
40
+ std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
41
+ img_data = (img_data - mean) / std
42
+
43
+ # zmiana ułożenia osi z [Wysokość, Szerokość, Kanały] na [Kanały, Wysokość, Szerokość]
44
+ img_data = np.transpose(img_data, (2, 0, 1))
45
+
46
+ # dodanie wymiaru batcha [1, 3, 224, 224]
47
+ img_data = np.expand_dims(img_data, axis=0)
48
+
49
+ return img_data
50
+
51
+ # GŁÓWNY ENDPOINT
52
+ @app.post("/predict-macro")
53
+ async def predict_macro(file: UploadFile = File(...)):
54
+ # odbiór pliku od użytkownika i wczytanie do RAM
55
+ contents = await file.read()
56
+ image = Image.open(io.BytesIO(contents))
57
+
58
+ # MLOps: Transformacja -> ONNS -> Odwrócenie skali
59
+ input_data = preprocess_image(image)
60
+ raw_output = session.run(None, {input_name: input_data})[0][0]
61
+
62
+ predicted_macro = raw_output * MAX_FACTORS
63
+
64
+ return {
65
+ "status": "success",
66
+ "filename": file.filename,
67
+ "predictions": {
68
+ "kcal": round(float(predicted_macro[0]), 0),
69
+ "fat_g": round(float(predicted_macro[1]), 0),
70
+ "carb_g": round(float(predicted_macro[2]), 0),
71
+ "protein_g": round(float(predicted_macro[3]), 0),
72
+ }
73
+ }
74
+
75
+ # Obsługuje główną strone
76
+ @app.get("/")
77
+ async def serve_frontend():
78
+ return FileResponse("index.html")
model_training.ipynb ADDED
@@ -0,0 +1,1688 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {
6
+ "id": "sjuMnYjkDZXT"
7
+ },
8
+ "source": [
9
+ "# Temat: ***Klasyfikacja makro składników posiłków***\n",
10
+ "\n",
11
+ "Dataset: https://huggingface.co/datasets/mmathys/food-nutrients"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "markdown",
16
+ "metadata": {},
17
+ "source": [
18
+ "## Przygotowanie datasetu"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "markdown",
23
+ "metadata": {
24
+ "id": "VkAX_NjlIPbd"
25
+ },
26
+ "source": [
27
+ "Importy"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "code",
32
+ "execution_count": 1,
33
+ "metadata": {
34
+ "id": "nWdktah-DUro"
35
+ },
36
+ "outputs": [],
37
+ "source": [
38
+ "import torch\n",
39
+ "import torch.nn as nn\n",
40
+ "import torchvision.models as models\n",
41
+ "import torch.optim as optim\n",
42
+ "import time\n",
43
+ "import pandas as pd\n",
44
+ "import numpy as np\n",
45
+ "import onnxruntime as ort\n",
46
+ "\n",
47
+ "from torchvision import transforms\n",
48
+ "from datasets import load_dataset, load_from_disk\n",
49
+ "from torch.utils.data import Dataset, DataLoader\n",
50
+ "from tabulate import tabulate\n",
51
+ "from onnxruntime.quantization import quantize_dynamic, QuantType"
52
+ ]
53
+ },
54
+ {
55
+ "cell_type": "markdown",
56
+ "metadata": {
57
+ "id": "Vn9Q92Z5IQue"
58
+ },
59
+ "source": [
60
+ "Pobranie datasetu z hugging face lub wczytanie z dysku"
61
+ ]
62
+ },
63
+ {
64
+ "cell_type": "code",
65
+ "execution_count": null,
66
+ "metadata": {
67
+ "id": "Wu-oDqOoD4hf"
68
+ },
69
+ "outputs": [],
70
+ "source": [
71
+ "# pobranie z hugging face\n",
72
+ "# dataset = load_dataset(\"mmathys/food-nutrients\", revision=\"refs/convert/parquet\")\n",
73
+ "\n",
74
+ "# wczytanie z dysku Google\n",
75
+ "split_dataset = load_from_disk('/content/drive/MyDrive/food_dataset') # <- ścieżka do datasetu"
76
+ ]
77
+ },
78
+ {
79
+ "cell_type": "markdown",
80
+ "metadata": {
81
+ "id": "k-lO8jVeyHCt"
82
+ },
83
+ "source": [
84
+ "Podzielenie datasetu"
85
+ ]
86
+ },
87
+ {
88
+ "cell_type": "code",
89
+ "execution_count": 3,
90
+ "metadata": {
91
+ "colab": {
92
+ "base_uri": "https://localhost:8080/",
93
+ "height": 223,
94
+ "referenced_widgets": [
95
+ "d3a8e790b1cf4b7b81cd14ced76c8b21",
96
+ "ffb88d550f134522bef235e4562e7703",
97
+ "ceb341ce3bbd4501a6a4571e5f90ef97",
98
+ "8afbb9695a364f87a5d8cb9179d0d797",
99
+ "e003619d59ca4e82bcffc06e674496de",
100
+ "c07a874cdc4a48b8bf3b1d8e8a89b6eb",
101
+ "0b6b96bd67fb4b1e86f85c64a03bed6e",
102
+ "58bd7fa138dd49fdba81c7aaaae7ee18",
103
+ "930be0d638704282bb2d152017e426b4",
104
+ "39505a3dd9fb4d4d9e0a87b196d34dcf",
105
+ "ef1da96b7eb9459cbe3137fa21d954a9",
106
+ "383d49a8573b4edaa8112bb3f1b0fdf5",
107
+ "3f30729bea374230be2518f856932d75",
108
+ "285067ea372542b0b33884ac9f673bdf",
109
+ "d87645ce5d2c443cab2d67adae2a59f3",
110
+ "0f2e8d3ced844f87a7716241727a05bb",
111
+ "6a9109178b124b658333522102e1d3d5",
112
+ "f606ffa8c38a4875bc02a8013e66eb9f",
113
+ "da60736bf83a40dba607d1790b18e1ec",
114
+ "50b0bf64a56140d7bce17eebacd4b458",
115
+ "fe58601fb3d14dddb4c6172693b89707",
116
+ "fb15837db0f64e0fbf057a1d8af5f8e7"
117
+ ]
118
+ },
119
+ "id": "qlInHE5Yxo_z",
120
+ "outputId": "2a09b347-c42b-45e1-a6f5-1df372595cc5"
121
+ },
122
+ "outputs": [
123
+ {
124
+ "name": "stdout",
125
+ "output_type": "stream",
126
+ "text": [
127
+ "Zdjęcia treningowe: 2608\n",
128
+ "Zdjęcia testowe: 652\n",
129
+ "\n",
130
+ "Dataset({\n",
131
+ " features: ['image', 'id', 'split', 'ingredients', 'total_calories', 'total_mass', 'total_fat', 'total_carb', 'total_protein'],\n",
132
+ " num_rows: 2608\n",
133
+ "})\n"
134
+ ]
135
+ }
136
+ ],
137
+ "source": [
138
+ "# split_dataset = dataset['test'].train_test_split(test_size=0.2, seed=42)\n",
139
+ "\n",
140
+ "# zapisanie datasetu na dysku Google\n",
141
+ "# split_dataset.save_to_disk('/content/drive/MyDrive/food_dataset')\n",
142
+ "\n",
143
+ "print(f\"Zdjęcia treningowe: {len(split_dataset['train'])}\")\n",
144
+ "print(f\"Zdjęcia testowe: {len(split_dataset['test'])}\")\n",
145
+ "print(f\"\\n{split_dataset['train']}\")"
146
+ ]
147
+ },
148
+ {
149
+ "cell_type": "markdown",
150
+ "metadata": {},
151
+ "source": [
152
+ "Wyznaczenie max wartości w celu skalowania wartości"
153
+ ]
154
+ },
155
+ {
156
+ "cell_type": "code",
157
+ "execution_count": 4,
158
+ "metadata": {},
159
+ "outputs": [
160
+ {
161
+ "name": "stdout",
162
+ "output_type": "stream",
163
+ "text": [
164
+ "Maksima -> Kcal: 3943.325195, Tłuszcz: 106.343002, Węgle: 844.568604, Białko: 143.492493\n"
165
+ ]
166
+ }
167
+ ],
168
+ "source": [
169
+ "train_features = split_dataset['train']\n",
170
+ "\n",
171
+ "max_kcal = max(train_features['total_calories'])\n",
172
+ "max_fat = max(train_features['total_fat'])\n",
173
+ "max_carb = max(train_features['total_carb'])\n",
174
+ "max_protein = max(train_features['total_protein'])\n",
175
+ "\n",
176
+ "print(f\"Maksima -> Kcal: {max_kcal}, Tłuszcz: {max_fat}, Węgle: {max_carb}, Białko: {max_protein}\")\n",
177
+ "\n",
178
+ "max_factors = np.array([max_kcal, max_fat, max_carb, max_protein])"
179
+ ]
180
+ },
181
+ {
182
+ "cell_type": "markdown",
183
+ "metadata": {
184
+ "id": "_qVBt7epIXLo"
185
+ },
186
+ "source": [
187
+ "Klasa Dataset"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "code",
192
+ "execution_count": 5,
193
+ "metadata": {
194
+ "colab": {
195
+ "base_uri": "https://localhost:8080/"
196
+ },
197
+ "id": "JMXGulIJII75",
198
+ "outputId": "2827e9dc-56dd-48d5-ec62-9ae6a9f9d8d3"
199
+ },
200
+ "outputs": [
201
+ {
202
+ "name": "stdout",
203
+ "output_type": "stream",
204
+ "text": [
205
+ "Kształt: torch.Size([3, 224, 224])\n",
206
+ "Wartości: tensor([0.0469, 0.0075, 0.0469, 0.0264])\n"
207
+ ]
208
+ }
209
+ ],
210
+ "source": [
211
+ "data_transforms = transforms.Compose([\n",
212
+ " transforms.Resize((224, 224)),\n",
213
+ " transforms.ToTensor(),\n",
214
+ " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # standardowe wartości dla ImageNet\n",
215
+ "])\n",
216
+ "\n",
217
+ "class FoodNutrientDataset(Dataset):\n",
218
+ " def __init__(self, hf_dataset, transform=None):\n",
219
+ " self.hf_dataset = hf_dataset\n",
220
+ " self.transform = transform\n",
221
+ "\n",
222
+ " def __len__(self):\n",
223
+ " return len(self.hf_dataset)\n",
224
+ "\n",
225
+ " def __getitem__(self, idx):\n",
226
+ " item = self.hf_dataset[idx]\n",
227
+ "\n",
228
+ " image = item['image'].convert(\"RGB\")\n",
229
+ "\n",
230
+ " if self.transform:\n",
231
+ " image = self.transform(image)\n",
232
+ "\n",
233
+ " # nazwy kluczy\n",
234
+ " kcal = float(item['total_calories']) / max_kcal\n",
235
+ " fat = float(item['total_fat']) / max_fat\n",
236
+ " carbs = float(item['total_carb']) / max_carb\n",
237
+ " protein = float(item['total_protein']) / max_protein\n",
238
+ "\n",
239
+ " targets = torch.tensor([kcal, fat, carbs, protein], dtype=torch.float32)\n",
240
+ "\n",
241
+ " return image, targets\n",
242
+ "\n",
243
+ "# instancje\n",
244
+ "train_dataset = FoodNutrientDataset(split_dataset['train'], transform=data_transforms)\n",
245
+ "test_dataset = FoodNutrientDataset(split_dataset['test'], transform=data_transforms)\n",
246
+ "\n",
247
+ "test_img, test_target = train_dataset[0]\n",
248
+ "print(f\"Kształt: {test_img.shape}\")\n",
249
+ "print(f\"Wartości: {test_target}\")"
250
+ ]
251
+ },
252
+ {
253
+ "cell_type": "markdown",
254
+ "metadata": {
255
+ "id": "Xr60ZnxMBdy2"
256
+ },
257
+ "source": [
258
+ "Przygotowanie paczek (DataLoaders)"
259
+ ]
260
+ },
261
+ {
262
+ "cell_type": "code",
263
+ "execution_count": 6,
264
+ "metadata": {
265
+ "id": "3sbGbwsbBc4i"
266
+ },
267
+ "outputs": [],
268
+ "source": [
269
+ "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n",
270
+ "test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)"
271
+ ]
272
+ },
273
+ {
274
+ "cell_type": "markdown",
275
+ "metadata": {
276
+ "id": "ioO4y_QLCy2g"
277
+ },
278
+ "source": [
279
+ "## Transfer Learning"
280
+ ]
281
+ },
282
+ {
283
+ "cell_type": "markdown",
284
+ "metadata": {
285
+ "id": "auclJzeYEKcz"
286
+ },
287
+ "source": [
288
+ "Pobranie modelu MobileNetV2 i przerobienie na model do regresji"
289
+ ]
290
+ },
291
+ {
292
+ "cell_type": "code",
293
+ "execution_count": 26,
294
+ "metadata": {
295
+ "colab": {
296
+ "base_uri": "https://localhost:8080/"
297
+ },
298
+ "id": "W1TA6bKxEJsD",
299
+ "outputId": "13a6e7e7-1a59-4940-c519-97539ddd0d4a"
300
+ },
301
+ "outputs": [
302
+ {
303
+ "name": "stdout",
304
+ "output_type": "stream",
305
+ "text": [
306
+ "Załadowano model na cuda\n"
307
+ ]
308
+ }
309
+ ],
310
+ "source": [
311
+ "weights = models.MobileNet_V2_Weights.DEFAULT\n",
312
+ "model = models.mobilenet_v2(weights=weights)\n",
313
+ "\n",
314
+ "# zamrożenie wszystkich warstw\n",
315
+ "for param in model.parameters():\n",
316
+ " param.requires_grad = False\n",
317
+ "\n",
318
+ "# odmrożenie 3 bloków features\n",
319
+ "for param in model.features[-3:].parameters():\n",
320
+ " param.requires_grad = True\n",
321
+ "\n",
322
+ "# podmiana ostatniej warstwy\n",
323
+ "# 4 wyjścia [Kalorie, Białko, Tłuszcz, Węglowodany]\n",
324
+ "num_ftrs = model.classifier[1].in_features\n",
325
+ "model.classifier[1] = nn.Sequential(\n",
326
+ " nn.Linear(num_ftrs, 256),\n",
327
+ " nn.ReLU(),\n",
328
+ " nn.Dropout(0.3),\n",
329
+ " nn.Linear(256, 4),\n",
330
+ " nn.ReLU()\n",
331
+ ")\n",
332
+ "\n",
333
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
334
+ "model = model.to(device)\n",
335
+ "\n",
336
+ "print(f\"Załadowano model na {device}\")\n",
337
+ "\n",
338
+ "# funkcja straty\n",
339
+ "# criterion = nn.MSELoss()\n",
340
+ "criterion = nn.HuberLoss(delta=50.0)\n",
341
+ "\n",
342
+ "# optimalizator (tylko dla nowych warstw)\n",
343
+ "optimizer = optim.Adam([\n",
344
+ " {'params': model.features[-3:].parameters(), 'lr': 0.0001},\n",
345
+ " {'params': model.classifier[1].parameters(), 'lr': 0.001}\n",
346
+ " ])\n",
347
+ "\n",
348
+ "# scheduler\n",
349
+ "scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n",
350
+ " optimizer, mode='min', factor=0.5, patience=5\n",
351
+ ")"
352
+ ]
353
+ },
354
+ {
355
+ "cell_type": "markdown",
356
+ "metadata": {
357
+ "id": "cnv-fMaNDeTl"
358
+ },
359
+ "source": [
360
+ "Trening"
361
+ ]
362
+ },
363
+ {
364
+ "cell_type": "code",
365
+ "execution_count": null,
366
+ "metadata": {
367
+ "colab": {
368
+ "base_uri": "https://localhost:8080/"
369
+ },
370
+ "id": "dcAtp2g_C0Ed",
371
+ "outputId": "f1276175-bd07-4c7a-bdad-46aee7d8a23f"
372
+ },
373
+ "outputs": [
374
+ {
375
+ "name": "stdout",
376
+ "output_type": "stream",
377
+ "text": [
378
+ "Rozpoczynam trening...\n",
379
+ "Epoka [1/100]...\n",
380
+ "Czas: 56.2s | Błąd treningowy (RMSE): 178.188561 | Błąd testowy (RMSE): 168.004262\n",
381
+ "Epoka [2/100]...\n",
382
+ "Czas: 55.8s | Błąd treningowy (RMSE): 169.397108 | Błąd testowy (RMSE): 167.980771\n",
383
+ "Epoka [3/100]...\n",
384
+ "Czas: 57.2s | Błąd treningowy (RMSE): 169.361251 | Błąd testowy (RMSE): 167.961045\n",
385
+ "Epoka [4/100]...\n",
386
+ "Czas: 57.3s | Błąd treningowy (RMSE): 169.332773 | Błąd testowy (RMSE): 167.945894\n",
387
+ "Epoka [5/100]...\n",
388
+ "Czas: 58.7s | Błąd treningowy (RMSE): 169.325557 | Błąd testowy (RMSE): 167.939000\n",
389
+ "Epoka [6/100]...\n",
390
+ "Czas: 54.9s | Błąd treningowy (RMSE): 169.225351 | Błąd testowy (RMSE): 167.864880\n",
391
+ "Epoka [7/100]...\n",
392
+ "Czas: 56.5s | Błąd treningowy (RMSE): 164.112868 | Błąd testowy (RMSE): 99.558664\n",
393
+ "Epoka [8/100]...\n",
394
+ "Czas: 56.1s | Błąd treningowy (RMSE): 77.103902 | Błąd testowy (RMSE): 52.589928\n",
395
+ "Epoka [9/100]...\n",
396
+ "Czas: 55.9s | Błąd treningowy (RMSE): 64.033303 | Błąd testowy (RMSE): 52.435742\n",
397
+ "Epoka [10/100]...\n",
398
+ "Czas: 56.5s | Błąd treningowy (RMSE): 57.499566 | Błąd testowy (RMSE): 58.920475\n",
399
+ "Epoka [11/100]...\n",
400
+ "Czas: 57.8s | Błąd treningowy (RMSE): 57.538209 | Błąd testowy (RMSE): 52.279127\n",
401
+ "Epoka [12/100]...\n",
402
+ "Czas: 59.0s | Błąd treningowy (RMSE): 57.607072 | Błąd testowy (RMSE): 50.357450\n",
403
+ "Epoka [13/100]...\n",
404
+ "Czas: 56.0s | Błąd treningowy (RMSE): 56.605721 | Błąd testowy (RMSE): 51.868502\n",
405
+ "Epoka [14/100]...\n",
406
+ "Czas: 55.6s | Błąd treningowy (RMSE): 53.386503 | Błąd testowy (RMSE): 58.271139\n",
407
+ "Epoka [15/100]...\n",
408
+ "Czas: 53.1s | Błąd treningowy (RMSE): 54.221051 | Błąd testowy (RMSE): 53.148526\n",
409
+ "Epoka [16/100]...\n",
410
+ "Czas: 52.7s | Błąd treningowy (RMSE): 52.917060 | Błąd testowy (RMSE): 52.019663\n",
411
+ "Epoka [17/100]...\n",
412
+ "Czas: 52.0s | Błąd treningowy (RMSE): 52.992275 | Błąd testowy (RMSE): 48.665368\n",
413
+ "Epoka [18/100]...\n",
414
+ "Czas: 52.5s | Błąd treningowy (RMSE): 50.237864 | Błąd testowy (RMSE): 50.677346\n",
415
+ "Epoka [19/100]...\n",
416
+ "Czas: 57.8s | Błąd treningowy (RMSE): 46.434908 | Błąd testowy (RMSE): 50.953815\n",
417
+ "Epoka [20/100]...\n",
418
+ "Czas: 56.9s | Błąd treningowy (RMSE): 45.531937 | Błąd testowy (RMSE): 49.074650\n",
419
+ "Epoka [21/100]...\n",
420
+ "Czas: 55.8s | Błąd treningowy (RMSE): 44.728347 | Błąd testowy (RMSE): 51.778983\n",
421
+ "Epoka [22/100]...\n",
422
+ "Czas: 56.5s | Błąd treningowy (RMSE): 41.428644 | Błąd testowy (RMSE): 48.853425\n",
423
+ "Epoka [23/100]...\n",
424
+ "Czas: 59.5s | Błąd treningowy (RMSE): 47.100133 | Błąd testowy (RMSE): 49.450177\n",
425
+ "Epoka [24/100]...\n",
426
+ "Czas: 56.7s | Błąd treningowy (RMSE): 41.668273 | Błąd testowy (RMSE): 50.537118\n",
427
+ "Epoka [25/100]...\n",
428
+ "Czas: 57.4s | Błąd treningowy (RMSE): 38.992410 | Błąd testowy (RMSE): 48.851822\n",
429
+ "Epoka [26/100]...\n",
430
+ "Czas: 54.1s | Błąd treningowy (RMSE): 41.042813 | Błąd testowy (RMSE): 48.137097\n",
431
+ "Epoka [27/100]...\n",
432
+ "Czas: 54.0s | Błąd treningowy (RMSE): 46.796708 | Błąd testowy (RMSE): 49.257832\n",
433
+ "Epoka [28/100]...\n",
434
+ "Czas: 54.0s | Błąd treningowy (RMSE): 46.009532 | Błąd testowy (RMSE): 47.618421\n",
435
+ "Epoka [29/100]...\n",
436
+ "Czas: 53.8s | Błąd treningowy (RMSE): 37.545192 | Błąd testowy (RMSE): 50.782361\n",
437
+ "Epoka [30/100]...\n",
438
+ "Czas: 54.5s | Błąd treningowy (RMSE): 37.893514 | Błąd testowy (RMSE): 47.197283\n",
439
+ "Epoka [31/100]...\n",
440
+ "Czas: 52.6s | Błąd treningowy (RMSE): 39.940901 | Błąd testowy (RMSE): 46.907932\n",
441
+ "Epoka [32/100]...\n",
442
+ "Czas: 54.1s | Błąd treningowy (RMSE): 37.930065 | Błąd testowy (RMSE): 47.868895\n",
443
+ "Epoka [33/100]...\n",
444
+ "Czas: 53.5s | Błąd treningowy (RMSE): 34.407337 | Błąd testowy (RMSE): 49.043571\n",
445
+ "Epoka [34/100]...\n",
446
+ "Czas: 53.1s | Błąd treningowy (RMSE): 34.219125 | Błąd testowy (RMSE): 47.288172\n",
447
+ "Epoka [35/100]...\n",
448
+ "Czas: 52.1s | Błąd treningowy (RMSE): 35.117653 | Błąd testowy (RMSE): 45.491266\n",
449
+ "Epoka [36/100]...\n",
450
+ "Czas: 51.4s | Błąd treningowy (RMSE): 33.759862 | Błąd testowy (RMSE): 48.359096\n",
451
+ "Epoka [37/100]...\n",
452
+ "Czas: 51.1s | Błąd treningowy (RMSE): 42.132784 | Błąd testowy (RMSE): 48.234712\n",
453
+ "Epoka [38/100]...\n",
454
+ "Czas: 51.1s | Błąd treningowy (RMSE): 36.024418 | Błąd testowy (RMSE): 46.255143\n",
455
+ "Epoka [39/100]...\n",
456
+ "Czas: 51.0s | Błąd treningowy (RMSE): 34.478403 | Błąd testowy (RMSE): 46.288933\n",
457
+ "Epoka [40/100]...\n",
458
+ "Czas: 51.3s | Błąd treningowy (RMSE): 33.229815 | Błąd testowy (RMSE): 46.113043\n",
459
+ "Epoka [41/100]...\n",
460
+ "Czas: 51.1s | Błąd treningowy (RMSE): 33.600531 | Błąd testowy (RMSE): 44.267916\n",
461
+ "Epoka [42/100]...\n",
462
+ "Czas: 51.4s | Błąd treningowy (RMSE): 34.830769 | Błąd testowy (RMSE): 45.718961\n",
463
+ "Epoka [43/100]...\n",
464
+ "Czas: 51.1s | Błąd treningowy (RMSE): 40.249838 | Błąd testowy (RMSE): 47.888996\n",
465
+ "Epoka [44/100]...\n",
466
+ "Czas: 51.1s | Błąd treningowy (RMSE): 43.058492 | Błąd testowy (RMSE): 45.792097\n",
467
+ "Epoka [45/100]...\n",
468
+ "Czas: 51.1s | Błąd treningowy (RMSE): 38.675054 | Błąd testowy (RMSE): 45.848031\n",
469
+ "Epoka [46/100]...\n",
470
+ "Czas: 51.0s | Błąd treningowy (RMSE): 31.439777 | Błąd testowy (RMSE): 46.967925\n",
471
+ "Epoka [47/100]...\n",
472
+ "Czas: 51.0s | Błąd treningowy (RMSE): 33.378058 | Błąd testowy (RMSE): 46.117315\n",
473
+ "Epoka [48/100]...\n",
474
+ "Czas: 51.2s | Błąd treningowy (RMSE): 31.249257 | Błąd testowy (RMSE): 46.905557\n",
475
+ "Epoka [49/100]...\n",
476
+ "Czas: 50.8s | Błąd treningowy (RMSE): 31.615665 | Błąd testowy (RMSE): 47.424634\n",
477
+ "Epoka [50/100]...\n",
478
+ "Czas: 51.0s | Błąd treningowy (RMSE): 30.728995 | Błąd testowy (RMSE): 47.000143\n",
479
+ "Epoka [51/100]...\n",
480
+ "Czas: 51.0s | Błąd treningowy (RMSE): 29.675208 | Błąd testowy (RMSE): 45.689271\n",
481
+ "Epoka [52/100]...\n",
482
+ "Czas: 51.0s | Błąd treningowy (RMSE): 32.260185 | Błąd testowy (RMSE): 45.121416\n",
483
+ "Epoka [53/100]...\n",
484
+ "Czas: 50.7s | Błąd treningowy (RMSE): 30.568070 | Błąd testowy (RMSE): 46.054914\n",
485
+ "Epoka [54/100]...\n",
486
+ "Czas: 50.9s | Błąd treningowy (RMSE): 31.322373 | Błąd testowy (RMSE): 46.634195\n",
487
+ "Epoka [55/100]...\n",
488
+ "Czas: 51.1s | Błąd treningowy (RMSE): 29.367186 | Błąd testowy (RMSE): 46.658814\n",
489
+ "Epoka [56/100]...\n",
490
+ "Czas: 51.6s | Błąd treningowy (RMSE): 30.754483 | Błąd testowy (RMSE): 45.815291\n",
491
+ "Epoka [57/100]...\n",
492
+ "Czas: 51.4s | Błąd treningowy (RMSE): 29.166818 | Błąd testowy (RMSE): 45.661158\n",
493
+ "Epoka [58/100]...\n",
494
+ "Czas: 52.6s | Błąd treningowy (RMSE): 30.426733 | Błąd testowy (RMSE): 46.018076\n",
495
+ "Epoka [59/100]...\n",
496
+ "Czas: 51.7s | Błąd treningowy (RMSE): 32.292654 | Błąd testowy (RMSE): 46.141985\n",
497
+ "Epoka [60/100]...\n",
498
+ "Czas: 51.1s | Błąd treningowy (RMSE): 29.627590 | Błąd testowy (RMSE): 45.483171\n",
499
+ "Epoka [61/100]...\n",
500
+ "Czas: 51.1s | Błąd treningowy (RMSE): 27.022041 | Błąd testowy (RMSE): 45.479725\n",
501
+ "Epoka [62/100]...\n",
502
+ "Czas: 51.4s | Błąd treningowy (RMSE): 28.119333 | Błąd testowy (RMSE): 45.083476\n",
503
+ "Epoka [63/100]...\n",
504
+ "Czas: 51.4s | Błąd treningowy (RMSE): 29.978329 | Błąd testowy (RMSE): 45.417601\n",
505
+ "Epoka [64/100]...\n",
506
+ "Czas: 51.4s | Błąd treningowy (RMSE): 35.413107 | Błąd testowy (RMSE): 45.002775\n",
507
+ "Epoka [65/100]...\n",
508
+ "Czas: 51.8s | Błąd treningowy (RMSE): 28.598748 | Błąd testowy (RMSE): 45.098285\n",
509
+ "Epoka [66/100]...\n",
510
+ "Czas: 51.3s | Błąd treningowy (RMSE): 27.512637 | Błąd testowy (RMSE): 45.033344\n",
511
+ "Epoka [67/100]...\n",
512
+ "Czas: 51.4s | Błąd treningowy (RMSE): 30.729560 | Błąd testowy (RMSE): 44.926249\n",
513
+ "Epoka [68/100]...\n",
514
+ "Czas: 52.3s | Błąd treningowy (RMSE): 27.634430 | Błąd testowy (RMSE): 44.983803\n",
515
+ "Epoka [69/100]...\n",
516
+ "Czas: 52.3s | Błąd treningowy (RMSE): 27.527840 | Błąd testowy (RMSE): 45.067444\n",
517
+ "Epoka [70/100]...\n",
518
+ "Czas: 51.3s | Błąd treningowy (RMSE): 32.309510 | Błąd testowy (RMSE): 45.459961\n",
519
+ "Epoka [71/100]...\n",
520
+ "Czas: 52.8s | Błąd treningowy (RMSE): 28.036586 | Błąd testowy (RMSE): 45.506729\n",
521
+ "Epoka [72/100]...\n",
522
+ "Czas: 51.6s | Błąd treningowy (RMSE): 27.291661 | Błąd testowy (RMSE): 45.453879\n",
523
+ "Epoka [73/100]...\n",
524
+ "Czas: 52.5s | Błąd treningowy (RMSE): 29.256362 | Błąd testowy (RMSE): 45.413326\n",
525
+ "Epoka [74/100]...\n",
526
+ "Czas: 51.4s | Błąd treningowy (RMSE): 28.189695 | Błąd testowy (RMSE): 45.519797\n",
527
+ "Epoka [75/100]...\n",
528
+ "Czas: 50.9s | Błąd treningowy (RMSE): 29.568075 | Błąd testowy (RMSE): 44.883508\n",
529
+ "Epoka [76/100]...\n",
530
+ "Czas: 50.9s | Błąd treningowy (RMSE): 29.721016 | Błąd testowy (RMSE): 45.545479\n",
531
+ "Epoka [77/100]...\n",
532
+ "Czas: 50.9s | Błąd treningowy (RMSE): 27.339565 | Błąd testowy (RMSE): 45.704246\n",
533
+ "Epoka [78/100]...\n",
534
+ "Czas: 50.8s | Błąd treningowy (RMSE): 30.446479 | Błąd testowy (RMSE): 45.286802\n",
535
+ "Epoka [79/100]...\n",
536
+ "Czas: 51.1s | Błąd treningowy (RMSE): 26.813800 | Błąd testowy (RMSE): 45.080516\n",
537
+ "Epoka [80/100]...\n",
538
+ "Czas: 51.3s | Błąd treningowy (RMSE): 37.333608 | Błąd testowy (RMSE): 45.825647\n",
539
+ "Epoka [81/100]...\n",
540
+ "Czas: 51.3s | Błąd treningowy (RMSE): 29.171336 | Błąd testowy (RMSE): 45.950801\n",
541
+ "Epoka [82/100]...\n",
542
+ "Czas: 51.0s | Błąd treningowy (RMSE): 27.917753 | Błąd testowy (RMSE): 45.960414\n",
543
+ "Epoka [83/100]...\n",
544
+ "Czas: 50.9s | Błąd treningowy (RMSE): 29.808054 | Błąd testowy (RMSE): 45.165492\n",
545
+ "Epoka [84/100]...\n",
546
+ "Czas: 51.0s | Błąd treningowy (RMSE): 28.590885 | Błąd testowy (RMSE): 45.355686\n",
547
+ "Epoka [85/100]...\n",
548
+ "Czas: 50.6s | Błąd treningowy (RMSE): 26.703351 | Błąd testowy (RMSE): 45.299842\n",
549
+ "Epoka [86/100]...\n",
550
+ "Czas: 50.6s | Błąd treningowy (RMSE): 27.528077 | Błąd testowy (RMSE): 45.696952\n",
551
+ "Epoka [87/100]...\n",
552
+ "Czas: 50.8s | Błąd treningowy (RMSE): 28.112270 | Błąd testowy (RMSE): 45.012691\n",
553
+ "Epoka [88/100]...\n",
554
+ "Czas: 50.6s | Błąd treningowy (RMSE): 28.337286 | Błąd testowy (RMSE): 45.180012\n",
555
+ "Epoka [89/100]...\n",
556
+ "Czas: 50.6s | Błąd treningowy (RMSE): 30.984244 | Błąd testowy (RMSE): 45.622681\n",
557
+ "Epoka [90/100]...\n",
558
+ "Czas: 50.6s | Błąd treningowy (RMSE): 26.309565 | Błąd testowy (RMSE): 45.704545\n",
559
+ "Epoka [91/100]...\n",
560
+ "Czas: 50.8s | Błąd treningowy (RMSE): 31.285457 | Błąd testowy (RMSE): 45.885330\n",
561
+ "Epoka [92/100]...\n",
562
+ "Czas: 50.4s | Błąd treningowy (RMSE): 27.869966 | Błąd testowy (RMSE): 45.417818\n",
563
+ "Epoka [93/100]...\n",
564
+ "Czas: 50.6s | Błąd treningowy (RMSE): 26.787253 | Błąd testowy (RMSE): 45.865132\n",
565
+ "Epoka [94/100]...\n",
566
+ "Czas: 50.5s | Błąd treningowy (RMSE): 27.359251 | Błąd testowy (RMSE): 45.640092\n",
567
+ "Epoka [95/100]...\n",
568
+ "Czas: 50.6s | Błąd treningowy (RMSE): 27.103805 | Błąd testowy (RMSE): 46.633001\n",
569
+ "Epoka [96/100]...\n",
570
+ "Czas: 50.5s | Błąd treningowy (RMSE): 31.908320 | Błąd testowy (RMSE): 45.638081\n",
571
+ "Epoka [97/100]...\n",
572
+ "Czas: 50.5s | Błąd treningowy (RMSE): 27.385738 | Błąd testowy (RMSE): 45.925380\n",
573
+ "Epoka [98/100]...\n",
574
+ "Czas: 50.4s | Błąd treningowy (RMSE): 29.219606 | Błąd testowy (RMSE): 45.558900\n",
575
+ "Epoka [99/100]...\n",
576
+ "Czas: 50.5s | Błąd treningowy (RMSE): 28.252443 | Błąd testowy (RMSE): 45.398595\n",
577
+ "Epoka [100/100]...\n",
578
+ "Czas: 50.7s | Błąd treningowy (RMSE): 27.743260 | Błąd testowy (RMSE): 45.271798\n",
579
+ "Trening zakończony.\n",
580
+ "Najlepszy model z loss 44.267916 zapisano w 'best_model.pth'\n"
581
+ ]
582
+ }
583
+ ],
584
+ "source": [
585
+ "# metryki do raportowania\n",
586
+ "metric_fn = nn.MSELoss()\n",
587
+ "\n",
588
+ "print(\"Rozpoczynam trening...\")\n",
589
+ "\n",
590
+ "num_epochs = 100\n",
591
+ "best_test_rmse = float('inf')\n",
592
+ "max_factors = torch.tensor([max_kcal, max_fat, max_carb, max_protein], dtype=torch.float32).to(device)\n",
593
+ "\n",
594
+ "for epoch in range(num_epochs):\n",
595
+ " print(f\"Epoka [{epoch+1}/{num_epochs}]...\")\n",
596
+ " start_time = time.time()\n",
597
+ "\n",
598
+ " # trening\n",
599
+ " model.train()\n",
600
+ " train_loss = 0.0\n",
601
+ "\n",
602
+ " for images, targets in train_loader:\n",
603
+ " images = images.to(device)\n",
604
+ " targets = targets.to(device)\n",
605
+ "\n",
606
+ " optimizer.zero_grad()\n",
607
+ "\n",
608
+ " outputs = model(images)\n",
609
+ " loss = criterion(outputs, targets)\n",
610
+ " loss.backward()\n",
611
+ " optimizer.step()\n",
612
+ "\n",
613
+ " # odwrócenie skalowania do wyliczenia błędu\n",
614
+ " with torch.no_grad():\n",
615
+ " unscaled_outputs = outputs * max_factors\n",
616
+ " unscaled_targets = targets * max_factors\n",
617
+ " real_mse = metric_fn(unscaled_outputs, unscaled_targets)\n",
618
+ " train_loss += real_mse.item() * images.size(0)\n",
619
+ "\n",
620
+ " epoch_train_rmse = (train_loss / len(train_loader.dataset)) ** 0.5\n",
621
+ "\n",
622
+ " # testy\n",
623
+ " model.eval()\n",
624
+ " test_loss = 0.0\n",
625
+ "\n",
626
+ " with torch.no_grad():\n",
627
+ " for images, targets in test_loader:\n",
628
+ " images = images.to(device)\n",
629
+ " targets = targets.to(device)\n",
630
+ "\n",
631
+ " outputs = model(images)\n",
632
+ "\n",
633
+ " unscaled_outputs = outputs * max_factors\n",
634
+ " unscaled_targets = targets * max_factors\n",
635
+ "\n",
636
+ " real_mse = metric_fn(unscaled_outputs, unscaled_targets)\n",
637
+ " test_loss += real_mse.item() * images.size(0)\n",
638
+ "\n",
639
+ " epoch_test_rmse = (test_loss / len(test_loader.dataset)) ** 0.5\n",
640
+ "\n",
641
+ " # automatyczne obniżenie learning_rate w przypadku stagnacji przez 5 epok\n",
642
+ " scheduler.step(epoch_test_rmse) \n",
643
+ "\n",
644
+ " end_time = time.time()\n",
645
+ "\n",
646
+ " print(f\"Czas: {end_time - start_time:.1f}s | \"\n",
647
+ " f\"Błąd treningowy (RMSE): {epoch_train_rmse:.6f} | \"\n",
648
+ " f\"Błąd testowy (RMSE): {epoch_test_rmse:.6f}\"\n",
649
+ " )\n",
650
+ "\n",
651
+ " if epoch_test_rmse < best_test_rmse:\n",
652
+ " best_test_rmse = epoch_test_rmse\n",
653
+ " torch.save(model.state_dict(), \"models/best_model.pth\")\n",
654
+ "\n",
655
+ "print(\"Trening zakończony.\")\n",
656
+ "print(f\"Najlepszy model z loss {best_test_rmse:.6f} zapisano w 'best_model.pth'\")"
657
+ ]
658
+ },
659
+ {
660
+ "cell_type": "markdown",
661
+ "metadata": {},
662
+ "source": [
663
+ "## Testy inferencji modeli\n",
664
+ "#### Model bazowy: **MobileNetV2**\n",
665
+ "\n",
666
+ "Inferencja na CPU\n",
667
+ "- model PyTorch\n",
668
+ "- model ONNX\n",
669
+ "- model ONNX kwantyzowany do INT8"
670
+ ]
671
+ },
672
+ {
673
+ "cell_type": "markdown",
674
+ "metadata": {},
675
+ "source": [
676
+ "Funkcja do inferencji modelu PyTorch"
677
+ ]
678
+ },
679
+ {
680
+ "cell_type": "code",
681
+ "execution_count": 7,
682
+ "metadata": {},
683
+ "outputs": [],
684
+ "source": [
685
+ "# funkcja do inferencji modelu\n",
686
+ "def inference_model(model, test_dataset, max_factors, name):\n",
687
+ " print(f\"\\nRozpoczynam testowanie modelu {name}...\")\n",
688
+ " model.eval()\n",
689
+ "\n",
690
+ " diff_all = []\n",
691
+ " inference_times = []\n",
692
+ "\n",
693
+ " for i in range(len(test_dataset)):\n",
694
+ " test_image, test_result = test_dataset[i]\n",
695
+ " test_image_batch = test_image.unsqueeze(0).to(device)\n",
696
+ "\n",
697
+ " # INFERENCJA\n",
698
+ " start_time_pt = time.perf_counter() # start\n",
699
+ "\n",
700
+ " with torch.no_grad():\n",
701
+ " raw_output = model(test_image_batch).numpy() # predykcja\n",
702
+ "\n",
703
+ " end_time_pt = time.perf_counter() # koniec\n",
704
+ " inference_time = (end_time_pt - start_time_pt) * 1000\n",
705
+ " inference_times.append(inference_time)\n",
706
+ "\n",
707
+ " predicted = raw_output[0] * max_factors\n",
708
+ " real_target = test_result.numpy() * max_factors\n",
709
+ "\n",
710
+ " diff = np.abs(predicted - real_target)\n",
711
+ " diff_all.append(diff)\n",
712
+ "\n",
713
+ " mean_diff = np.nanmean(np.array(diff_all), axis=0)\n",
714
+ " mean_inference = np.mean(inference_times)\n",
715
+ "\n",
716
+ " print(f\"\\n=== WYNIKI INFERENCJI [{name}] ===\")\n",
717
+ " df = pd.DataFrame([\n",
718
+ " [\"Średni błąd (MAE)\"] + [f\"{v:.3f}\" for v in mean_diff] + [f\"{mean_inference:.2f} ms\"]\n",
719
+ " ])\n",
720
+ "\n",
721
+ " headers = [\"\", \"[KCAL]\", \"[FAT]\", \"[CARB]\", \"[PROTEIN]\", \"Średni czas Inferencji\"]\n",
722
+ "\n",
723
+ "\n",
724
+ " print(tabulate(df, headers=headers, tablefmt='grid', showindex=False))\n"
725
+ ]
726
+ },
727
+ {
728
+ "cell_type": "markdown",
729
+ "metadata": {},
730
+ "source": [
731
+ "PyTorch (CPU) MobileNetV2"
732
+ ]
733
+ },
734
+ {
735
+ "cell_type": "code",
736
+ "execution_count": 9,
737
+ "metadata": {},
738
+ "outputs": [
739
+ {
740
+ "name": "stdout",
741
+ "output_type": "stream",
742
+ "text": [
743
+ "Załadowano model z pliku na cpu\n"
744
+ ]
745
+ },
746
+ {
747
+ "name": "stderr",
748
+ "output_type": "stream",
749
+ "text": [
750
+ "C:\\Users\\Kacper\\AppData\\Local\\Temp\\ipykernel_12660\\1426864496.py:18: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
751
+ " model_mobilenet.load_state_dict(torch.load('models/best_model.pth', map_location=device))\n"
752
+ ]
753
+ }
754
+ ],
755
+ "source": [
756
+ "# stworzenie pustego modelu\n",
757
+ "model_mobilenet = models.mobilenet_v2()\n",
758
+ "\n",
759
+ "# podmiana ostatniej warstwy\n",
760
+ "# 4 wyjścia [Kalorie, Białko, Tłuszcz, Węglowodany]\n",
761
+ "num_ftrs = model_mobilenet.classifier[1].in_features\n",
762
+ "model_mobilenet.classifier[1] = nn.Sequential(\n",
763
+ " nn.Linear(num_ftrs, 256),\n",
764
+ " nn.ReLU(),\n",
765
+ " nn.Dropout(0.3),\n",
766
+ " nn.Linear(256, 4),\n",
767
+ " nn.ReLU()\n",
768
+ ")\n",
769
+ "\n",
770
+ "device = \"cpu\"\n",
771
+ "model_mobilenet = model_mobilenet.to(device)\n",
772
+ "\n",
773
+ "model_mobilenet.load_state_dict(torch.load('models/best_model.pth', map_location=device))\n",
774
+ "\n",
775
+ "print(f\"Załadowano model z pliku na {device}\")"
776
+ ]
777
+ },
778
+ {
779
+ "cell_type": "markdown",
780
+ "metadata": {},
781
+ "source": [
782
+ "ONNX MobileNetV2 + kwantyzacja do INT8"
783
+ ]
784
+ },
785
+ {
786
+ "cell_type": "code",
787
+ "execution_count": null,
788
+ "metadata": {},
789
+ "outputs": [
790
+ {
791
+ "name": "stdout",
792
+ "output_type": "stream",
793
+ "text": [
794
+ "Zapisano model ONNX (onnx_model.onnx)\n"
795
+ ]
796
+ },
797
+ {
798
+ "name": "stderr",
799
+ "output_type": "stream",
800
+ "text": [
801
+ "WARNING:root:Please consider to run pre-processing before quantization. Refer to example: https://github.com/microsoft/onnxruntime-inference-examples/blob/main/quantization/image_classification/cpu/ReadMe.md \n"
802
+ ]
803
+ },
804
+ {
805
+ "name": "stdout",
806
+ "output_type": "stream",
807
+ "text": [
808
+ "Zapisano skwantyzowany model ONNX INT8 (int8_model.onnx)\n"
809
+ ]
810
+ }
811
+ ],
812
+ "source": [
813
+ "# atrapa danych\n",
814
+ "dummy_input = torch.randn(1, 3, 224, 224).to('cpu')\n",
815
+ "\n",
816
+ "# użyty model z PyTorch\n",
817
+ "model_mobilenet.to('cpu')\n",
818
+ "model_mobilenet.eval()\n",
819
+ "\n",
820
+ "# EXPORT DO FORMATU ONNX\n",
821
+ "onnx_model_path = \"models/onnx_model.onnx\"\n",
822
+ "\n",
823
+ "torch.onnx.export(\n",
824
+ " model_mobilenet, # model PyTorch\n",
825
+ " dummy_input, # atrapa wejścia\n",
826
+ " onnx_model_path, # ścieżka do zapisu\n",
827
+ " export_params=True, # zapisz wyuczone wagi\n",
828
+ " opset_version=14, # wersja standardu ONNX\n",
829
+ " do_constant_folding=True, # optymalizacja matematyczna grafu\n",
830
+ " input_names=['input'], # nazwa wejścia\n",
831
+ " output_names=['output'], # nazwa wyjścia\n",
832
+ " dynamic_axes={ # pozwala na wrzucenie wielu zdjęć naraz\n",
833
+ " 'input': {0: 'batch_size'},\n",
834
+ " 'output': {0: 'batch_size'}\n",
835
+ " }\n",
836
+ ")\n",
837
+ "print(f\"Zapisano model ONNX ({onnx_model_path})\")\n",
838
+ "\n",
839
+ "# KWANTYZACJA DO INT8\n",
840
+ "quantized_model_path = \"int8_model.onnx\"\n",
841
+ "\n",
842
+ "# kwantyzacja ucina precyzje wag, zmniejszając wage modelu i przyspieszając inferencje na procesorze\n",
843
+ "quantize_dynamic(\n",
844
+ " model_input=onnx_model_path, # ścieżka modelu który chcemy kwantyzować\n",
845
+ " model_output=quantized_model_path, # ścieżka do zapisu\n",
846
+ " weight_type=QuantType.QUInt8 # precyzja wag\n",
847
+ ")\n",
848
+ "print(f\"Zapisano skwantyzowany model ONNX INT8 ({quantized_model_path})\")"
849
+ ]
850
+ },
851
+ {
852
+ "cell_type": "markdown",
853
+ "metadata": {},
854
+ "source": [
855
+ "Funkcja do inferencji modelu ONNX"
856
+ ]
857
+ },
858
+ {
859
+ "cell_type": "code",
860
+ "execution_count": 10,
861
+ "metadata": {},
862
+ "outputs": [],
863
+ "source": [
864
+ "def inference_model_onnx(onnx_path, test_dataset, max_factors, name):\n",
865
+ " # załadowanie silnika ONNX\n",
866
+ " session = ort.InferenceSession(onnx_path, providers=['CPUExecutionProvider'])\n",
867
+ "\n",
868
+ " # nazwa wejścia definiowana przy exporcie\n",
869
+ " input_name = session.get_inputs()[0].name\n",
870
+ "\n",
871
+ " print(f\"\\nRozpoczynam testowanie modelu {name}...\")\n",
872
+ "\n",
873
+ " diff_all = []\n",
874
+ " inference_times = []\n",
875
+ "\n",
876
+ " for i in range(len(test_dataset)):\n",
877
+ " test_image, test_result = test_dataset[i]\n",
878
+ " test_image_batch = test_image.unsqueeze(0).numpy() # numpy() kluczowe\n",
879
+ "\n",
880
+ " # INFERENCJA\n",
881
+ " start_time = time.perf_counter() # start\n",
882
+ "\n",
883
+ " raw_output = session.run(None, {input_name: test_image_batch})[0]\n",
884
+ "\n",
885
+ " end_time = time.perf_counter() # koniec\n",
886
+ " inference_time = (end_time - start_time) * 1000\n",
887
+ " inference_times.append(inference_time)\n",
888
+ "\n",
889
+ " predicted = raw_output[0] * max_factors\n",
890
+ " real_target = test_result.numpy() * max_factors\n",
891
+ "\n",
892
+ " diff = np.abs(predicted - real_target)\n",
893
+ " diff_all.append(diff)\n",
894
+ "\n",
895
+ " mean_diff = np.nanmean(np.array(diff_all), axis=0)\n",
896
+ " mean_inference = np.mean(inference_times)\n",
897
+ "\n",
898
+ " print(f\"\\n=== WYNIKI INFERENCJI [{name}] ===\")\n",
899
+ " df = pd.DataFrame([\n",
900
+ " [\"Średni błąd (MAE)\"] + [f\"{v:.3f}\" for v in mean_diff] + [f\"{mean_inference:.2f} ms\"]\n",
901
+ " ])\n",
902
+ "\n",
903
+ " headers = [\"\", \"[KCAL]\", \"[FAT]\", \"[CARB]\", \"[PROTEIN]\", \"Średni czas Inferencji\"]\n",
904
+ "\n",
905
+ "\n",
906
+ " print(tabulate(df, headers=headers, tablefmt='grid', showindex=False))\n",
907
+ " "
908
+ ]
909
+ },
910
+ {
911
+ "cell_type": "markdown",
912
+ "metadata": {},
913
+ "source": [
914
+ "### TEST INFERENCJI"
915
+ ]
916
+ },
917
+ {
918
+ "cell_type": "code",
919
+ "execution_count": 11,
920
+ "metadata": {},
921
+ "outputs": [
922
+ {
923
+ "name": "stdout",
924
+ "output_type": "stream",
925
+ "text": [
926
+ "\n",
927
+ "Rozpoczynam testowanie modelu PyTorch [CPU]...\n",
928
+ "\n",
929
+ "=== WYNIKI INFERENCJI [PyTorch [CPU]] ===\n",
930
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n",
931
+ "| | [KCAL] | [FAT] | [CARB] | [PROTEIN] | Średni czas Inferencji |\n",
932
+ "+===================+==========+=========+==========+=============+==========================+\n",
933
+ "| Średni błąd (MAE) | 63.28 | 3.878 | 8.389 | 5.317 | 40.70 ms |\n",
934
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n",
935
+ "\n",
936
+ "Rozpoczynam testowanie modelu ONNX [CPU]...\n",
937
+ "\n",
938
+ "=== WYNIKI INFERENCJI [ONNX [CPU]] ===\n",
939
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n",
940
+ "| | [KCAL] | [FAT] | [CARB] | [PROTEIN] | Średni czas Inferencji |\n",
941
+ "+===================+==========+=========+==========+=============+==========================+\n",
942
+ "| Średni błąd (MAE) | 63.28 | 3.878 | 8.389 | 5.317 | 6.65 ms |\n",
943
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n",
944
+ "\n",
945
+ "Rozpoczynam testowanie modelu ONNX INT8 [CPU]...\n",
946
+ "\n",
947
+ "=== WYNIKI INFERENCJI [ONNX INT8 [CPU]] ===\n",
948
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n",
949
+ "| | [KCAL] | [FAT] | [CARB] | [PROTEIN] | Średni czas Inferencji |\n",
950
+ "+===================+==========+=========+==========+=============+==========================+\n",
951
+ "| Średni błąd (MAE) | 92.687 | 5.91 | 10.404 | 7.359 | 41.38 ms |\n",
952
+ "+-------------------+----------+---------+----------+-------------+--------------------------+\n"
953
+ ]
954
+ }
955
+ ],
956
+ "source": [
957
+ "inference_model(model_mobilenet, test_dataset, max_factors, \"PyTorch [CPU]\")\n",
958
+ "\n",
959
+ "inference_model_onnx(\"models/onnx_model.onnx\", test_dataset, max_factors, \"ONNX [CPU]\")\n",
960
+ "\n",
961
+ "inference_model_onnx(\"models/int8_model.onnx\", test_dataset, max_factors, \"ONNX INT8 [CPU]\")"
962
+ ]
963
+ },
964
+ {
965
+ "cell_type": "markdown",
966
+ "metadata": {},
967
+ "source": [
968
+ "#### Wniosek\n",
969
+ "\n",
970
+ "Najlepszy model to **ONNX (bez kwantyzacji)**, osiągnął on najkrótszy czas inferencji (6 ms) + najlepsze rezultaty (takie same jak czysty PyTorch), model ten zostanie wykorzystany do realizacji API projektu."
971
+ ]
972
+ }
973
+ ],
974
+ "metadata": {
975
+ "accelerator": "GPU",
976
+ "colab": {
977
+ "gpuType": "T4",
978
+ "provenance": []
979
+ },
980
+ "kernelspec": {
981
+ "display_name": "food_ml",
982
+ "language": "python",
983
+ "name": "python3"
984
+ },
985
+ "language_info": {
986
+ "codemirror_mode": {
987
+ "name": "ipython",
988
+ "version": 3
989
+ },
990
+ "file_extension": ".py",
991
+ "mimetype": "text/x-python",
992
+ "name": "python",
993
+ "nbconvert_exporter": "python",
994
+ "pygments_lexer": "ipython3",
995
+ "version": "3.10.20"
996
+ },
997
+ "widgets": {
998
+ "application/vnd.jupyter.widget-state+json": {
999
+ "0b6b96bd67fb4b1e86f85c64a03bed6e": {
1000
+ "model_module": "@jupyter-widgets/controls",
1001
+ "model_module_version": "1.5.0",
1002
+ "model_name": "DescriptionStyleModel",
1003
+ "state": {
1004
+ "_model_module": "@jupyter-widgets/controls",
1005
+ "_model_module_version": "1.5.0",
1006
+ "_model_name": "DescriptionStyleModel",
1007
+ "_view_count": null,
1008
+ "_view_module": "@jupyter-widgets/base",
1009
+ "_view_module_version": "1.2.0",
1010
+ "_view_name": "StyleView",
1011
+ "description_width": ""
1012
+ }
1013
+ },
1014
+ "0f2e8d3ced844f87a7716241727a05bb": {
1015
+ "model_module": "@jupyter-widgets/base",
1016
+ "model_module_version": "1.2.0",
1017
+ "model_name": "LayoutModel",
1018
+ "state": {
1019
+ "_model_module": "@jupyter-widgets/base",
1020
+ "_model_module_version": "1.2.0",
1021
+ "_model_name": "LayoutModel",
1022
+ "_view_count": null,
1023
+ "_view_module": "@jupyter-widgets/base",
1024
+ "_view_module_version": "1.2.0",
1025
+ "_view_name": "LayoutView",
1026
+ "align_content": null,
1027
+ "align_items": null,
1028
+ "align_self": null,
1029
+ "border": null,
1030
+ "bottom": null,
1031
+ "display": null,
1032
+ "flex": null,
1033
+ "flex_flow": null,
1034
+ "grid_area": null,
1035
+ "grid_auto_columns": null,
1036
+ "grid_auto_flow": null,
1037
+ "grid_auto_rows": null,
1038
+ "grid_column": null,
1039
+ "grid_gap": null,
1040
+ "grid_row": null,
1041
+ "grid_template_areas": null,
1042
+ "grid_template_columns": null,
1043
+ "grid_template_rows": null,
1044
+ "height": null,
1045
+ "justify_content": null,
1046
+ "justify_items": null,
1047
+ "left": null,
1048
+ "margin": null,
1049
+ "max_height": null,
1050
+ "max_width": null,
1051
+ "min_height": null,
1052
+ "min_width": null,
1053
+ "object_fit": null,
1054
+ "object_position": null,
1055
+ "order": null,
1056
+ "overflow": null,
1057
+ "overflow_x": null,
1058
+ "overflow_y": null,
1059
+ "padding": null,
1060
+ "right": null,
1061
+ "top": null,
1062
+ "visibility": null,
1063
+ "width": null
1064
+ }
1065
+ },
1066
+ "285067ea372542b0b33884ac9f673bdf": {
1067
+ "model_module": "@jupyter-widgets/controls",
1068
+ "model_module_version": "1.5.0",
1069
+ "model_name": "FloatProgressModel",
1070
+ "state": {
1071
+ "_dom_classes": [],
1072
+ "_model_module": "@jupyter-widgets/controls",
1073
+ "_model_module_version": "1.5.0",
1074
+ "_model_name": "FloatProgressModel",
1075
+ "_view_count": null,
1076
+ "_view_module": "@jupyter-widgets/controls",
1077
+ "_view_module_version": "1.5.0",
1078
+ "_view_name": "ProgressView",
1079
+ "bar_style": "success",
1080
+ "description": "",
1081
+ "description_tooltip": null,
1082
+ "layout": "IPY_MODEL_da60736bf83a40dba607d1790b18e1ec",
1083
+ "max": 652,
1084
+ "min": 0,
1085
+ "orientation": "horizontal",
1086
+ "style": "IPY_MODEL_50b0bf64a56140d7bce17eebacd4b458",
1087
+ "value": 652
1088
+ }
1089
+ },
1090
+ "383d49a8573b4edaa8112bb3f1b0fdf5": {
1091
+ "model_module": "@jupyter-widgets/controls",
1092
+ "model_module_version": "1.5.0",
1093
+ "model_name": "HBoxModel",
1094
+ "state": {
1095
+ "_dom_classes": [],
1096
+ "_model_module": "@jupyter-widgets/controls",
1097
+ "_model_module_version": "1.5.0",
1098
+ "_model_name": "HBoxModel",
1099
+ "_view_count": null,
1100
+ "_view_module": "@jupyter-widgets/controls",
1101
+ "_view_module_version": "1.5.0",
1102
+ "_view_name": "HBoxView",
1103
+ "box_style": "",
1104
+ "children": [
1105
+ "IPY_MODEL_3f30729bea374230be2518f856932d75",
1106
+ "IPY_MODEL_285067ea372542b0b33884ac9f673bdf",
1107
+ "IPY_MODEL_d87645ce5d2c443cab2d67adae2a59f3"
1108
+ ],
1109
+ "layout": "IPY_MODEL_0f2e8d3ced844f87a7716241727a05bb"
1110
+ }
1111
+ },
1112
+ "39505a3dd9fb4d4d9e0a87b196d34dcf": {
1113
+ "model_module": "@jupyter-widgets/base",
1114
+ "model_module_version": "1.2.0",
1115
+ "model_name": "LayoutModel",
1116
+ "state": {
1117
+ "_model_module": "@jupyter-widgets/base",
1118
+ "_model_module_version": "1.2.0",
1119
+ "_model_name": "LayoutModel",
1120
+ "_view_count": null,
1121
+ "_view_module": "@jupyter-widgets/base",
1122
+ "_view_module_version": "1.2.0",
1123
+ "_view_name": "LayoutView",
1124
+ "align_content": null,
1125
+ "align_items": null,
1126
+ "align_self": null,
1127
+ "border": null,
1128
+ "bottom": null,
1129
+ "display": null,
1130
+ "flex": null,
1131
+ "flex_flow": null,
1132
+ "grid_area": null,
1133
+ "grid_auto_columns": null,
1134
+ "grid_auto_flow": null,
1135
+ "grid_auto_rows": null,
1136
+ "grid_column": null,
1137
+ "grid_gap": null,
1138
+ "grid_row": null,
1139
+ "grid_template_areas": null,
1140
+ "grid_template_columns": null,
1141
+ "grid_template_rows": null,
1142
+ "height": null,
1143
+ "justify_content": null,
1144
+ "justify_items": null,
1145
+ "left": null,
1146
+ "margin": null,
1147
+ "max_height": null,
1148
+ "max_width": null,
1149
+ "min_height": null,
1150
+ "min_width": null,
1151
+ "object_fit": null,
1152
+ "object_position": null,
1153
+ "order": null,
1154
+ "overflow": null,
1155
+ "overflow_x": null,
1156
+ "overflow_y": null,
1157
+ "padding": null,
1158
+ "right": null,
1159
+ "top": null,
1160
+ "visibility": null,
1161
+ "width": null
1162
+ }
1163
+ },
1164
+ "3f30729bea374230be2518f856932d75": {
1165
+ "model_module": "@jupyter-widgets/controls",
1166
+ "model_module_version": "1.5.0",
1167
+ "model_name": "HTMLModel",
1168
+ "state": {
1169
+ "_dom_classes": [],
1170
+ "_model_module": "@jupyter-widgets/controls",
1171
+ "_model_module_version": "1.5.0",
1172
+ "_model_name": "HTMLModel",
1173
+ "_view_count": null,
1174
+ "_view_module": "@jupyter-widgets/controls",
1175
+ "_view_module_version": "1.5.0",
1176
+ "_view_name": "HTMLView",
1177
+ "description": "",
1178
+ "description_tooltip": null,
1179
+ "layout": "IPY_MODEL_6a9109178b124b658333522102e1d3d5",
1180
+ "placeholder": "​",
1181
+ "style": "IPY_MODEL_f606ffa8c38a4875bc02a8013e66eb9f",
1182
+ "value": "Saving the dataset (1/1 shards): 100%"
1183
+ }
1184
+ },
1185
+ "50b0bf64a56140d7bce17eebacd4b458": {
1186
+ "model_module": "@jupyter-widgets/controls",
1187
+ "model_module_version": "1.5.0",
1188
+ "model_name": "ProgressStyleModel",
1189
+ "state": {
1190
+ "_model_module": "@jupyter-widgets/controls",
1191
+ "_model_module_version": "1.5.0",
1192
+ "_model_name": "ProgressStyleModel",
1193
+ "_view_count": null,
1194
+ "_view_module": "@jupyter-widgets/base",
1195
+ "_view_module_version": "1.2.0",
1196
+ "_view_name": "StyleView",
1197
+ "bar_color": null,
1198
+ "description_width": ""
1199
+ }
1200
+ },
1201
+ "58bd7fa138dd49fdba81c7aaaae7ee18": {
1202
+ "model_module": "@jupyter-widgets/base",
1203
+ "model_module_version": "1.2.0",
1204
+ "model_name": "LayoutModel",
1205
+ "state": {
1206
+ "_model_module": "@jupyter-widgets/base",
1207
+ "_model_module_version": "1.2.0",
1208
+ "_model_name": "LayoutModel",
1209
+ "_view_count": null,
1210
+ "_view_module": "@jupyter-widgets/base",
1211
+ "_view_module_version": "1.2.0",
1212
+ "_view_name": "LayoutView",
1213
+ "align_content": null,
1214
+ "align_items": null,
1215
+ "align_self": null,
1216
+ "border": null,
1217
+ "bottom": null,
1218
+ "display": null,
1219
+ "flex": null,
1220
+ "flex_flow": null,
1221
+ "grid_area": null,
1222
+ "grid_auto_columns": null,
1223
+ "grid_auto_flow": null,
1224
+ "grid_auto_rows": null,
1225
+ "grid_column": null,
1226
+ "grid_gap": null,
1227
+ "grid_row": null,
1228
+ "grid_template_areas": null,
1229
+ "grid_template_columns": null,
1230
+ "grid_template_rows": null,
1231
+ "height": null,
1232
+ "justify_content": null,
1233
+ "justify_items": null,
1234
+ "left": null,
1235
+ "margin": null,
1236
+ "max_height": null,
1237
+ "max_width": null,
1238
+ "min_height": null,
1239
+ "min_width": null,
1240
+ "object_fit": null,
1241
+ "object_position": null,
1242
+ "order": null,
1243
+ "overflow": null,
1244
+ "overflow_x": null,
1245
+ "overflow_y": null,
1246
+ "padding": null,
1247
+ "right": null,
1248
+ "top": null,
1249
+ "visibility": null,
1250
+ "width": null
1251
+ }
1252
+ },
1253
+ "6a9109178b124b658333522102e1d3d5": {
1254
+ "model_module": "@jupyter-widgets/base",
1255
+ "model_module_version": "1.2.0",
1256
+ "model_name": "LayoutModel",
1257
+ "state": {
1258
+ "_model_module": "@jupyter-widgets/base",
1259
+ "_model_module_version": "1.2.0",
1260
+ "_model_name": "LayoutModel",
1261
+ "_view_count": null,
1262
+ "_view_module": "@jupyter-widgets/base",
1263
+ "_view_module_version": "1.2.0",
1264
+ "_view_name": "LayoutView",
1265
+ "align_content": null,
1266
+ "align_items": null,
1267
+ "align_self": null,
1268
+ "border": null,
1269
+ "bottom": null,
1270
+ "display": null,
1271
+ "flex": null,
1272
+ "flex_flow": null,
1273
+ "grid_area": null,
1274
+ "grid_auto_columns": null,
1275
+ "grid_auto_flow": null,
1276
+ "grid_auto_rows": null,
1277
+ "grid_column": null,
1278
+ "grid_gap": null,
1279
+ "grid_row": null,
1280
+ "grid_template_areas": null,
1281
+ "grid_template_columns": null,
1282
+ "grid_template_rows": null,
1283
+ "height": null,
1284
+ "justify_content": null,
1285
+ "justify_items": null,
1286
+ "left": null,
1287
+ "margin": null,
1288
+ "max_height": null,
1289
+ "max_width": null,
1290
+ "min_height": null,
1291
+ "min_width": null,
1292
+ "object_fit": null,
1293
+ "object_position": null,
1294
+ "order": null,
1295
+ "overflow": null,
1296
+ "overflow_x": null,
1297
+ "overflow_y": null,
1298
+ "padding": null,
1299
+ "right": null,
1300
+ "top": null,
1301
+ "visibility": null,
1302
+ "width": null
1303
+ }
1304
+ },
1305
+ "8afbb9695a364f87a5d8cb9179d0d797": {
1306
+ "model_module": "@jupyter-widgets/controls",
1307
+ "model_module_version": "1.5.0",
1308
+ "model_name": "HTMLModel",
1309
+ "state": {
1310
+ "_dom_classes": [],
1311
+ "_model_module": "@jupyter-widgets/controls",
1312
+ "_model_module_version": "1.5.0",
1313
+ "_model_name": "HTMLModel",
1314
+ "_view_count": null,
1315
+ "_view_module": "@jupyter-widgets/controls",
1316
+ "_view_module_version": "1.5.0",
1317
+ "_view_name": "HTMLView",
1318
+ "description": "",
1319
+ "description_tooltip": null,
1320
+ "layout": "IPY_MODEL_39505a3dd9fb4d4d9e0a87b196d34dcf",
1321
+ "placeholder": "​",
1322
+ "style": "IPY_MODEL_ef1da96b7eb9459cbe3137fa21d954a9",
1323
+ "value": " 2608/2608 [00:18&lt;00:00, 140.10 examples/s]"
1324
+ }
1325
+ },
1326
+ "930be0d638704282bb2d152017e426b4": {
1327
+ "model_module": "@jupyter-widgets/controls",
1328
+ "model_module_version": "1.5.0",
1329
+ "model_name": "ProgressStyleModel",
1330
+ "state": {
1331
+ "_model_module": "@jupyter-widgets/controls",
1332
+ "_model_module_version": "1.5.0",
1333
+ "_model_name": "ProgressStyleModel",
1334
+ "_view_count": null,
1335
+ "_view_module": "@jupyter-widgets/base",
1336
+ "_view_module_version": "1.2.0",
1337
+ "_view_name": "StyleView",
1338
+ "bar_color": null,
1339
+ "description_width": ""
1340
+ }
1341
+ },
1342
+ "c07a874cdc4a48b8bf3b1d8e8a89b6eb": {
1343
+ "model_module": "@jupyter-widgets/base",
1344
+ "model_module_version": "1.2.0",
1345
+ "model_name": "LayoutModel",
1346
+ "state": {
1347
+ "_model_module": "@jupyter-widgets/base",
1348
+ "_model_module_version": "1.2.0",
1349
+ "_model_name": "LayoutModel",
1350
+ "_view_count": null,
1351
+ "_view_module": "@jupyter-widgets/base",
1352
+ "_view_module_version": "1.2.0",
1353
+ "_view_name": "LayoutView",
1354
+ "align_content": null,
1355
+ "align_items": null,
1356
+ "align_self": null,
1357
+ "border": null,
1358
+ "bottom": null,
1359
+ "display": null,
1360
+ "flex": null,
1361
+ "flex_flow": null,
1362
+ "grid_area": null,
1363
+ "grid_auto_columns": null,
1364
+ "grid_auto_flow": null,
1365
+ "grid_auto_rows": null,
1366
+ "grid_column": null,
1367
+ "grid_gap": null,
1368
+ "grid_row": null,
1369
+ "grid_template_areas": null,
1370
+ "grid_template_columns": null,
1371
+ "grid_template_rows": null,
1372
+ "height": null,
1373
+ "justify_content": null,
1374
+ "justify_items": null,
1375
+ "left": null,
1376
+ "margin": null,
1377
+ "max_height": null,
1378
+ "max_width": null,
1379
+ "min_height": null,
1380
+ "min_width": null,
1381
+ "object_fit": null,
1382
+ "object_position": null,
1383
+ "order": null,
1384
+ "overflow": null,
1385
+ "overflow_x": null,
1386
+ "overflow_y": null,
1387
+ "padding": null,
1388
+ "right": null,
1389
+ "top": null,
1390
+ "visibility": null,
1391
+ "width": null
1392
+ }
1393
+ },
1394
+ "ceb341ce3bbd4501a6a4571e5f90ef97": {
1395
+ "model_module": "@jupyter-widgets/controls",
1396
+ "model_module_version": "1.5.0",
1397
+ "model_name": "FloatProgressModel",
1398
+ "state": {
1399
+ "_dom_classes": [],
1400
+ "_model_module": "@jupyter-widgets/controls",
1401
+ "_model_module_version": "1.5.0",
1402
+ "_model_name": "FloatProgressModel",
1403
+ "_view_count": null,
1404
+ "_view_module": "@jupyter-widgets/controls",
1405
+ "_view_module_version": "1.5.0",
1406
+ "_view_name": "ProgressView",
1407
+ "bar_style": "success",
1408
+ "description": "",
1409
+ "description_tooltip": null,
1410
+ "layout": "IPY_MODEL_58bd7fa138dd49fdba81c7aaaae7ee18",
1411
+ "max": 2608,
1412
+ "min": 0,
1413
+ "orientation": "horizontal",
1414
+ "style": "IPY_MODEL_930be0d638704282bb2d152017e426b4",
1415
+ "value": 2608
1416
+ }
1417
+ },
1418
+ "d3a8e790b1cf4b7b81cd14ced76c8b21": {
1419
+ "model_module": "@jupyter-widgets/controls",
1420
+ "model_module_version": "1.5.0",
1421
+ "model_name": "HBoxModel",
1422
+ "state": {
1423
+ "_dom_classes": [],
1424
+ "_model_module": "@jupyter-widgets/controls",
1425
+ "_model_module_version": "1.5.0",
1426
+ "_model_name": "HBoxModel",
1427
+ "_view_count": null,
1428
+ "_view_module": "@jupyter-widgets/controls",
1429
+ "_view_module_version": "1.5.0",
1430
+ "_view_name": "HBoxView",
1431
+ "box_style": "",
1432
+ "children": [
1433
+ "IPY_MODEL_ffb88d550f134522bef235e4562e7703",
1434
+ "IPY_MODEL_ceb341ce3bbd4501a6a4571e5f90ef97",
1435
+ "IPY_MODEL_8afbb9695a364f87a5d8cb9179d0d797"
1436
+ ],
1437
+ "layout": "IPY_MODEL_e003619d59ca4e82bcffc06e674496de"
1438
+ }
1439
+ },
1440
+ "d87645ce5d2c443cab2d67adae2a59f3": {
1441
+ "model_module": "@jupyter-widgets/controls",
1442
+ "model_module_version": "1.5.0",
1443
+ "model_name": "HTMLModel",
1444
+ "state": {
1445
+ "_dom_classes": [],
1446
+ "_model_module": "@jupyter-widgets/controls",
1447
+ "_model_module_version": "1.5.0",
1448
+ "_model_name": "HTMLModel",
1449
+ "_view_count": null,
1450
+ "_view_module": "@jupyter-widgets/controls",
1451
+ "_view_module_version": "1.5.0",
1452
+ "_view_name": "HTMLView",
1453
+ "description": "",
1454
+ "description_tooltip": null,
1455
+ "layout": "IPY_MODEL_fe58601fb3d14dddb4c6172693b89707",
1456
+ "placeholder": "​",
1457
+ "style": "IPY_MODEL_fb15837db0f64e0fbf057a1d8af5f8e7",
1458
+ "value": " 652/652 [00:03&lt;00:00, 217.00 examples/s]"
1459
+ }
1460
+ },
1461
+ "da60736bf83a40dba607d1790b18e1ec": {
1462
+ "model_module": "@jupyter-widgets/base",
1463
+ "model_module_version": "1.2.0",
1464
+ "model_name": "LayoutModel",
1465
+ "state": {
1466
+ "_model_module": "@jupyter-widgets/base",
1467
+ "_model_module_version": "1.2.0",
1468
+ "_model_name": "LayoutModel",
1469
+ "_view_count": null,
1470
+ "_view_module": "@jupyter-widgets/base",
1471
+ "_view_module_version": "1.2.0",
1472
+ "_view_name": "LayoutView",
1473
+ "align_content": null,
1474
+ "align_items": null,
1475
+ "align_self": null,
1476
+ "border": null,
1477
+ "bottom": null,
1478
+ "display": null,
1479
+ "flex": null,
1480
+ "flex_flow": null,
1481
+ "grid_area": null,
1482
+ "grid_auto_columns": null,
1483
+ "grid_auto_flow": null,
1484
+ "grid_auto_rows": null,
1485
+ "grid_column": null,
1486
+ "grid_gap": null,
1487
+ "grid_row": null,
1488
+ "grid_template_areas": null,
1489
+ "grid_template_columns": null,
1490
+ "grid_template_rows": null,
1491
+ "height": null,
1492
+ "justify_content": null,
1493
+ "justify_items": null,
1494
+ "left": null,
1495
+ "margin": null,
1496
+ "max_height": null,
1497
+ "max_width": null,
1498
+ "min_height": null,
1499
+ "min_width": null,
1500
+ "object_fit": null,
1501
+ "object_position": null,
1502
+ "order": null,
1503
+ "overflow": null,
1504
+ "overflow_x": null,
1505
+ "overflow_y": null,
1506
+ "padding": null,
1507
+ "right": null,
1508
+ "top": null,
1509
+ "visibility": null,
1510
+ "width": null
1511
+ }
1512
+ },
1513
+ "e003619d59ca4e82bcffc06e674496de": {
1514
+ "model_module": "@jupyter-widgets/base",
1515
+ "model_module_version": "1.2.0",
1516
+ "model_name": "LayoutModel",
1517
+ "state": {
1518
+ "_model_module": "@jupyter-widgets/base",
1519
+ "_model_module_version": "1.2.0",
1520
+ "_model_name": "LayoutModel",
1521
+ "_view_count": null,
1522
+ "_view_module": "@jupyter-widgets/base",
1523
+ "_view_module_version": "1.2.0",
1524
+ "_view_name": "LayoutView",
1525
+ "align_content": null,
1526
+ "align_items": null,
1527
+ "align_self": null,
1528
+ "border": null,
1529
+ "bottom": null,
1530
+ "display": null,
1531
+ "flex": null,
1532
+ "flex_flow": null,
1533
+ "grid_area": null,
1534
+ "grid_auto_columns": null,
1535
+ "grid_auto_flow": null,
1536
+ "grid_auto_rows": null,
1537
+ "grid_column": null,
1538
+ "grid_gap": null,
1539
+ "grid_row": null,
1540
+ "grid_template_areas": null,
1541
+ "grid_template_columns": null,
1542
+ "grid_template_rows": null,
1543
+ "height": null,
1544
+ "justify_content": null,
1545
+ "justify_items": null,
1546
+ "left": null,
1547
+ "margin": null,
1548
+ "max_height": null,
1549
+ "max_width": null,
1550
+ "min_height": null,
1551
+ "min_width": null,
1552
+ "object_fit": null,
1553
+ "object_position": null,
1554
+ "order": null,
1555
+ "overflow": null,
1556
+ "overflow_x": null,
1557
+ "overflow_y": null,
1558
+ "padding": null,
1559
+ "right": null,
1560
+ "top": null,
1561
+ "visibility": null,
1562
+ "width": null
1563
+ }
1564
+ },
1565
+ "ef1da96b7eb9459cbe3137fa21d954a9": {
1566
+ "model_module": "@jupyter-widgets/controls",
1567
+ "model_module_version": "1.5.0",
1568
+ "model_name": "DescriptionStyleModel",
1569
+ "state": {
1570
+ "_model_module": "@jupyter-widgets/controls",
1571
+ "_model_module_version": "1.5.0",
1572
+ "_model_name": "DescriptionStyleModel",
1573
+ "_view_count": null,
1574
+ "_view_module": "@jupyter-widgets/base",
1575
+ "_view_module_version": "1.2.0",
1576
+ "_view_name": "StyleView",
1577
+ "description_width": ""
1578
+ }
1579
+ },
1580
+ "f606ffa8c38a4875bc02a8013e66eb9f": {
1581
+ "model_module": "@jupyter-widgets/controls",
1582
+ "model_module_version": "1.5.0",
1583
+ "model_name": "DescriptionStyleModel",
1584
+ "state": {
1585
+ "_model_module": "@jupyter-widgets/controls",
1586
+ "_model_module_version": "1.5.0",
1587
+ "_model_name": "DescriptionStyleModel",
1588
+ "_view_count": null,
1589
+ "_view_module": "@jupyter-widgets/base",
1590
+ "_view_module_version": "1.2.0",
1591
+ "_view_name": "StyleView",
1592
+ "description_width": ""
1593
+ }
1594
+ },
1595
+ "fb15837db0f64e0fbf057a1d8af5f8e7": {
1596
+ "model_module": "@jupyter-widgets/controls",
1597
+ "model_module_version": "1.5.0",
1598
+ "model_name": "DescriptionStyleModel",
1599
+ "state": {
1600
+ "_model_module": "@jupyter-widgets/controls",
1601
+ "_model_module_version": "1.5.0",
1602
+ "_model_name": "DescriptionStyleModel",
1603
+ "_view_count": null,
1604
+ "_view_module": "@jupyter-widgets/base",
1605
+ "_view_module_version": "1.2.0",
1606
+ "_view_name": "StyleView",
1607
+ "description_width": ""
1608
+ }
1609
+ },
1610
+ "fe58601fb3d14dddb4c6172693b89707": {
1611
+ "model_module": "@jupyter-widgets/base",
1612
+ "model_module_version": "1.2.0",
1613
+ "model_name": "LayoutModel",
1614
+ "state": {
1615
+ "_model_module": "@jupyter-widgets/base",
1616
+ "_model_module_version": "1.2.0",
1617
+ "_model_name": "LayoutModel",
1618
+ "_view_count": null,
1619
+ "_view_module": "@jupyter-widgets/base",
1620
+ "_view_module_version": "1.2.0",
1621
+ "_view_name": "LayoutView",
1622
+ "align_content": null,
1623
+ "align_items": null,
1624
+ "align_self": null,
1625
+ "border": null,
1626
+ "bottom": null,
1627
+ "display": null,
1628
+ "flex": null,
1629
+ "flex_flow": null,
1630
+ "grid_area": null,
1631
+ "grid_auto_columns": null,
1632
+ "grid_auto_flow": null,
1633
+ "grid_auto_rows": null,
1634
+ "grid_column": null,
1635
+ "grid_gap": null,
1636
+ "grid_row": null,
1637
+ "grid_template_areas": null,
1638
+ "grid_template_columns": null,
1639
+ "grid_template_rows": null,
1640
+ "height": null,
1641
+ "justify_content": null,
1642
+ "justify_items": null,
1643
+ "left": null,
1644
+ "margin": null,
1645
+ "max_height": null,
1646
+ "max_width": null,
1647
+ "min_height": null,
1648
+ "min_width": null,
1649
+ "object_fit": null,
1650
+ "object_position": null,
1651
+ "order": null,
1652
+ "overflow": null,
1653
+ "overflow_x": null,
1654
+ "overflow_y": null,
1655
+ "padding": null,
1656
+ "right": null,
1657
+ "top": null,
1658
+ "visibility": null,
1659
+ "width": null
1660
+ }
1661
+ },
1662
+ "ffb88d550f134522bef235e4562e7703": {
1663
+ "model_module": "@jupyter-widgets/controls",
1664
+ "model_module_version": "1.5.0",
1665
+ "model_name": "HTMLModel",
1666
+ "state": {
1667
+ "_dom_classes": [],
1668
+ "_model_module": "@jupyter-widgets/controls",
1669
+ "_model_module_version": "1.5.0",
1670
+ "_model_name": "HTMLModel",
1671
+ "_view_count": null,
1672
+ "_view_module": "@jupyter-widgets/controls",
1673
+ "_view_module_version": "1.5.0",
1674
+ "_view_name": "HTMLView",
1675
+ "description": "",
1676
+ "description_tooltip": null,
1677
+ "layout": "IPY_MODEL_c07a874cdc4a48b8bf3b1d8e8a89b6eb",
1678
+ "placeholder": "​",
1679
+ "style": "IPY_MODEL_0b6b96bd67fb4b1e86f85c64a03bed6e",
1680
+ "value": "Saving the dataset (3/3 shards): 100%"
1681
+ }
1682
+ }
1683
+ }
1684
+ }
1685
+ },
1686
+ "nbformat": 4,
1687
+ "nbformat_minor": 0
1688
+ }
models/best_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:57faf4c4ab9e05100d57faeafd4e1483b457fd584328b0441fed29a09c24d0f7
3
+ size 10457402
models/int8_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99f9bf7ac84040490ab68bd59c0644df806bbc5d6a76e0ad7ee7d9cf1c0cde49
3
+ size 2741589
models/onnx_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2c0d45fb6d6b17aa74902ba8daa9cb12c0ea4232d5c41cc4f7436e51d01c50ce
3
+ size 10184809
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0.post1
3
+ python-multipart==0.0.6
4
+ onnxruntime==1.16.3
5
+ Pillow==10.1.0
6
+ numpy==1.26.2
static/logo.png ADDED

Git LFS Details

  • SHA256: 1851b82f4e249dc553013137e55e840060830b5d1841db9ffca4e4308ada5fe7
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
static/script.js ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 1. ZDJĘCIA Z DATASETU
2
+ const hardcodedMeals = [
3
+ { name: "Stek z warzywami", image: "/test_images/stek_z_warzywami.jpg", macros: { kcal: 525, p: 48, f: 35, c: 6 } },
4
+ { name: "Mini pizza salami", image: "/test_images/mini_pizza_salami.jpg", macros: { kcal: 132, p: 6, f: 6, c: 15 } },
5
+ { name: "Jajecznica z brokułem", image: "/test_images/jajecznica_z_brokulem.jpg", macros: { kcal: 296, p: 19, f: 21, c: 7 } },
6
+ { name: "Mix sałat", image: "/test_images/miska_salaty.jpg", macros: { kcal: 20, p: 1, f: 0, c: 4 } },
7
+ { name: "Bekon", image: "/test_images/bekon.jpg", macros: { kcal: 470, p: 32, f: 36, c: 1 } },
8
+ { name: "Owsianka z owocami", image: "/test_images/owsianka_z_owocami.jpg", macros: { kcal: 155, p: 5, f: 3, c: 29 } }
9
+ ];
10
+
11
+ // 2. ZDJĘCIA SPOZA DATASETU
12
+ const externalMeals = [
13
+ { name: "Burger drwala", image: "/test_images/burger_drwala.png", macros: { kcal: 915, p: 39, f: 52, c: 70 } },
14
+ { name: "Kotelty drobiowe z ziemniakami i sałatą", image: "/test_images/kotelty_z_ziemniakami.jpeg", macros: { kcal: 590, p: 61, f: 11, c: 63 } },
15
+ { name: "Spaghetti bolognese", image: "/test_images/spaghetti-bolognese.jpg", macros: { kcal: 405, p: 21, f: 17, c: 43 } },
16
+ { name: "SUPER bowl", image: "/test_images/super_bowl.png", macros: { kcal: 716, p: 33, f: 42, c: 60 } }
17
+ ];
18
+
19
+ // 3. POSIŁKI UŻYTKOWNIKÓW
20
+ let userMeals = JSON.parse(localStorage.getItem('savedUserMeals')) || [];
21
+
22
+ let currentFile = null;
23
+ let currentTruth = null;
24
+ let activeTab = null;
25
+
26
+ // Słownik do przywracania oryginalnych nazw przycisków
27
+ const btnOriginalText = {
28
+ test: "🗂️ Dataset",
29
+ external: "🌍 Spoza Datasetu",
30
+ user: "👤 Użytkownik"
31
+ };
32
+
33
+ window.onload = () => {};
34
+
35
+ function openSidebar(tab) {
36
+ const sidebar = document.getElementById('sidebar');
37
+ const btns = {
38
+ test: document.getElementById('btnTest'),
39
+ external: document.getElementById('btnExternal'),
40
+ user: document.getElementById('btnUser')
41
+ };
42
+
43
+ // Resetujemy wszystkie przyciski do stanu domyślnego
44
+ for (let key in btns) {
45
+ btns[key].classList.remove('open');
46
+ btns[key].classList.remove('shifted'); // Usuwamy przesunięcie
47
+ btns[key].style.backgroundColor = "#333";
48
+ btns[key].style.color = "white";
49
+ btns[key].innerText = btnOriginalText[key];
50
+ }
51
+
52
+ // Jeśli kliknięto aktywny panel -> zamknij i przerwij (wszystko wraca na prawo)
53
+ if (activeTab === tab && sidebar.classList.contains('open')) {
54
+ sidebar.classList.remove('open');
55
+ activeTab = null;
56
+ return;
57
+ }
58
+
59
+ // Otwarcie nowego panelu
60
+ sidebar.classList.add('open');
61
+ activeTab = tab;
62
+
63
+ // Przesuwamy WSZYSTKIE przyciski na lewo (do krawędzi panelu)
64
+ for (let key in btns) {
65
+ btns[key].classList.add('shifted');
66
+ }
67
+
68
+ // Stylizacja TYLKO aktywnego przycisku i zmiana tekstu na emotkę
69
+ btns[tab].classList.add('open');
70
+ btns[tab].style.backgroundColor = "#00ff88";
71
+ btns[tab].style.color = "#121212";
72
+ btns[tab].innerText = "▶︎▶︎▶︎"; // Emotka zastępująca ">>> Zamknij"
73
+
74
+ // Wybór odpowiedniej zawartości do wyświetlenia
75
+ if (tab === 'test') {
76
+ renderList(hardcodedMeals, 'test');
77
+ } else if (tab === 'external') {
78
+ renderList(externalMeals, 'external');
79
+ } else {
80
+ renderList(userMeals, 'user');
81
+ }
82
+ }
83
+
84
+ function renderList(mealsArray, type) {
85
+ const list = document.getElementById('mealsList');
86
+ const form = document.getElementById('addMealForm');
87
+ const title = document.getElementById('sidebarTitle');
88
+ const desc = document.getElementById('sidebarDesc');
89
+
90
+ list.innerHTML = "";
91
+
92
+ if (type === 'test') {
93
+ title.innerText = "Baza Datasetu";
94
+ desc.innerText = "Oryginalne posiłki wycięte ze zbioru testowego.";
95
+ form.style.display = 'none';
96
+ if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555;'>Brak zdjęć w skrypcie.</p>";
97
+ } else if (type === 'external') {
98
+ title.innerText = "Spoza Datasetu";
99
+ desc.innerText = "Testy na zdjęciach spoza zbioru.";
100
+ form.style.display = 'none';
101
+ if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555;'>Brak zewnętrznych zdjęć.</p>";
102
+ } else {
103
+ title.innerText = "Posiłki Użytkownika";
104
+ desc.innerText = "Zapisywane lokalnie w przeglądarce.";
105
+ form.style.display = 'block';
106
+ if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555; text-align:center;'>Dodaj pierwszy posiłek poniżej!</p>";
107
+ }
108
+
109
+ mealsArray.forEach((meal, index) => {
110
+ list.innerHTML += `
111
+ <div class="meal-card" onclick="loadMealIntoAnalyzer(${index}, '${type}')">
112
+ <img src="${meal.image}" alt="Zdjęcie">
113
+ <div class="meal-card-title">${meal.name}</div>
114
+ <div class="meal-card-macro">
115
+ 🔥 ${meal.macros.kcal} kcal | 🍗 ${meal.macros.p}g | 🥑 ${meal.macros.f}g | 🍞 ${meal.macros.c}g
116
+ </div>
117
+ </div>
118
+ `;
119
+ });
120
+ }
121
+
122
+ function previewNewImage(event) {
123
+ if(event.target.files.length === 0) return;
124
+ currentFile = event.target.files[0];
125
+ currentTruth = null;
126
+ setupPreview(URL.createObjectURL(currentFile), "Tryb: Nieznane zdjęcie (Z dysku)");
127
+ }
128
+
129
+ async function loadMealIntoAnalyzer(index, type) {
130
+ let meal;
131
+ if (type === 'test') meal = hardcodedMeals[index];
132
+ else if (type === 'external') meal = externalMeals[index];
133
+ else meal = userMeals[index];
134
+
135
+ currentTruth = meal.macros;
136
+
137
+ try {
138
+ const res = await fetch(meal.image);
139
+ const blob = await res.blob();
140
+ currentFile = new File([blob], "test_image.jpg", { type: "image/jpeg" });
141
+ setupPreview(meal.image, `Tryb: Ground Truth (${meal.name})`);
142
+
143
+ if(window.innerWidth < 800) openSidebar(activeTab);
144
+ } catch (err) {
145
+ alert("Błąd ładowania! Upewnij się, że zdjęcie jest fizycznie w folderze.");
146
+ }
147
+ }
148
+
149
+ function setupPreview(imageSrc, modeText) {
150
+ const preview = document.getElementById('preview');
151
+ const modeInd = document.getElementById('modeIndicator');
152
+
153
+ preview.src = imageSrc;
154
+ preview.style.display = 'block';
155
+ document.getElementById('analyzeBtn').disabled = false;
156
+ document.getElementById('resultsPanel').style.display = 'none';
157
+
158
+ modeInd.innerText = modeText;
159
+ modeInd.style.color = currentTruth ? "#ffaa00" : "#aaa";
160
+ }
161
+
162
+ async function analyzeImage() {
163
+ if (!currentFile) return;
164
+
165
+ const btn = document.getElementById('analyzeBtn');
166
+ btn.disabled = true;
167
+ btn.innerText = "⏳ ONNX analizuje...";
168
+
169
+ const formData = new FormData();
170
+ formData.append("file", currentFile);
171
+
172
+ try {
173
+ const response = await fetch('/predict-macro', {
174
+ method: 'POST',
175
+ body: formData
176
+ });
177
+ const data = await response.json();
178
+ displayResults(data.predictions);
179
+ } catch (error) {
180
+ alert("Błąd serwera! Upewnij się, że FastAPI działa.");
181
+ console.error(error);
182
+ } finally {
183
+ btn.disabled = false;
184
+ btn.innerText = "Analizuj Makro";
185
+ }
186
+ }
187
+
188
+ function displayResults(preds) {
189
+ document.getElementById('res-kcal').innerText = preds.kcal + " kcal";
190
+ document.getElementById('res-protein').innerText = preds.protein_g + " g";
191
+ document.getElementById('res-fat').innerText = preds.fat_g + " g";
192
+ document.getElementById('res-carb').innerText = preds.carb_g + " g";
193
+
194
+ const truthCols = document.querySelectorAll('.truth-col');
195
+
196
+ if (currentTruth) {
197
+ truthCols.forEach(col => col.style.display = 'table-cell');
198
+
199
+ document.getElementById('truth-kcal').innerText = currentTruth.kcal + " kcal";
200
+ document.getElementById('truth-protein').innerText = currentTruth.p + " g";
201
+ document.getElementById('truth-fat').innerText = currentTruth.f + " g";
202
+ document.getElementById('truth-carb').innerText = currentTruth.c + " g";
203
+
204
+ document.getElementById('diff-kcal').innerText = Math.abs((preds.kcal - currentTruth.kcal).toFixed(1)) + " kcal";
205
+ document.getElementById('diff-protein').innerText = Math.abs((preds.protein_g - currentTruth.p).toFixed(1)) + " g";
206
+ document.getElementById('diff-fat').innerText = Math.abs((preds.fat_g - currentTruth.f).toFixed(1)) + " g";
207
+ document.getElementById('diff-carb').innerText = Math.abs((preds.carb_g - currentTruth.c).toFixed(1)) + " g";
208
+ } else {
209
+ truthCols.forEach(col => col.style.display = 'none');
210
+ }
211
+
212
+ document.getElementById('resultsPanel').style.display = 'block';
213
+ }
214
+
215
+ function addUserMeal() {
216
+ const fileInput = document.getElementById('newMealImage');
217
+ const name = document.getElementById('newMealName').value;
218
+ const kcal = document.getElementById('newMealKcal').value;
219
+ const p = document.getElementById('newMealP').value;
220
+ const f = document.getElementById('newMealF').value;
221
+ const c = document.getElementById('newMealC').value;
222
+
223
+ if(!fileInput.files[0] || !name || !kcal) {
224
+ alert("Wypełnij nazwę, zdjęcie i chociaż kalorie!");
225
+ return;
226
+ }
227
+
228
+ const reader = new FileReader();
229
+ reader.onload = function(e) {
230
+ const newMeal = {
231
+ name: name,
232
+ image: e.target.result,
233
+ macros: { kcal: kcal, p: p||0, f: f||0, c: c||0 }
234
+ };
235
+
236
+ userMeals.push(newMeal);
237
+ localStorage.setItem('savedUserMeals', JSON.stringify(userMeals));
238
+
239
+ document.getElementById('newMealName').value = "";
240
+ fileInput.value = "";
241
+ renderList(userMeals, 'user');
242
+ };
243
+ reader.readAsDataURL(fileInput.files[0]);
244
+ }
static/style.css ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
3
+ background-color: #121212;
4
+ color: #ffffff;
5
+ margin: 0;
6
+ overflow-x: hidden;
7
+ display: flex;
8
+ }
9
+
10
+ .main-panel {
11
+ flex-grow: 1;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ min-height: 100vh;
16
+ padding: 2rem;
17
+ transition: 0.3s;
18
+ }
19
+
20
+ .container {
21
+ background-color: #1e1e1e;
22
+ padding: 2rem;
23
+ border-radius: 12px;
24
+ box-shadow: 0 8px 16px rgba(0,0,0,0.5);
25
+ width: 100%;
26
+ max-width: 500px;
27
+ text-align: center;
28
+ }
29
+
30
+ h1 { color: #00ff88; margin-top: 0; }
31
+ .mode-indicator { color: #aaa; font-size: 0.9rem; margin-bottom: 20px; }
32
+
33
+ input[type="file"] { display: none; }
34
+
35
+ .custom-file-upload {
36
+ border: 2px dashed #00ff88;
37
+ padding: 1.5rem;
38
+ cursor: pointer;
39
+ border-radius: 8px;
40
+ display: block;
41
+ margin-bottom: 1rem;
42
+ transition: 0.3s;
43
+ }
44
+
45
+ .custom-file-upload:hover { background-color: rgba(0, 255, 136, 0.1); }
46
+
47
+ #preview {
48
+ max-width: 100%;
49
+ border-radius: 8px;
50
+ margin-bottom: 1rem;
51
+ display: none;
52
+ max-height: 300px;
53
+ object-fit: cover;
54
+ width: 100%;
55
+ }
56
+
57
+ button {
58
+ background-color: #00ff88;
59
+ color: #121212;
60
+ border: none;
61
+ padding: 10px 20px;
62
+ font-size: 1rem;
63
+ font-weight: bold;
64
+ border-radius: 6px;
65
+ cursor: pointer;
66
+ width: 100%;
67
+ transition: 0.3s;
68
+ }
69
+
70
+ button:hover { background-color: #00cc6a; }
71
+ button:disabled { background-color: #555; cursor: not-allowed; }
72
+
73
+ .results {
74
+ margin-top: 1.5rem;
75
+ text-align: left;
76
+ display: none;
77
+ background-color: #2a2a2a;
78
+ padding: 1rem;
79
+ border-radius: 8px;
80
+ }
81
+
82
+ table {
83
+ width: 100%;
84
+ border-collapse: collapse;
85
+ margin-top: 10px;
86
+ }
87
+
88
+ th, td {
89
+ text-align: left;
90
+ padding: 8px;
91
+ border-bottom: 1px solid #444;
92
+ }
93
+
94
+ th { color: #888; font-size: 0.9rem; }
95
+ .emoji-col { width: 30px; text-align: center; }
96
+ .val-model { color: #00ff88; font-weight: bold; }
97
+ .val-truth { color: #ffaa00; }
98
+ .val-diff { color: #ff4444; font-size: 0.9rem; }
99
+
100
+ .sidebar {
101
+ position: fixed;
102
+ right: -400px;
103
+ top: 0;
104
+ width: 350px;
105
+ height: 100vh;
106
+ background-color: #1a1a1a;
107
+ border-left: 1px solid #333;
108
+ transition: right 0.3s ease-in-out;
109
+ padding: 1.5rem;
110
+ box-sizing: border-box;
111
+ overflow-y: auto;
112
+ z-index: 100;
113
+ }
114
+
115
+ .sidebar.open { right: 0; }
116
+ #sidebarTitle { color: #00ff88; margin-top: 0; }
117
+ #sidebarDesc { font-size: 0.85rem; color: #888; }
118
+
119
+ /* PRZYCISKI ZAKŁADEK (Poziome, bez rotacji) */
120
+ /* PRZYCISKI ZAKŁADEK */
121
+ .toggle-btn {
122
+ position: fixed;
123
+ right: 20px;
124
+ background-color: #333;
125
+ color: white;
126
+ width: 180px;
127
+ height: 45px; /* Sztywna wysokość wszystkich przycisków */
128
+ box-sizing: border-box;
129
+ display: flex; /* Utrzymuje tekst idealnie na środku */
130
+ align-items: center;
131
+ justify-content: center;
132
+ z-index: 101;
133
+ transition: all 0.3s ease-in-out;
134
+ border-radius: 6px;
135
+ box-shadow: -2px 0 5px rgba(0,0,0,0.5);
136
+ }
137
+
138
+ /* Pozycje startowe (obliczone pod 45px wysokości + 10px odstępu) */
139
+ #btnTest { top: 20px; }
140
+ #btnExternal { top: 75px; }
141
+ #btnUser { top: 130px; }
142
+
143
+ /* Nowa klasa: Kiedy JAKIKOLWIEK panel jest otwarty, przesuwamy wszystkie przyciski */
144
+ .toggle-btn.shifted {
145
+ right: 350px;
146
+ }
147
+
148
+ /* Kiedy KONKRETNY przycisk jest aktywny - zmienia kolor i łączy się z panelem */
149
+ .toggle-btn.open {
150
+ background-color: #00ff88;
151
+ color: #121212;
152
+ border-radius: 6px 0 0 6px;
153
+ }
154
+
155
+ .meal-card {
156
+ background-color: #2a2a2a;
157
+ border-radius: 8px;
158
+ padding: 10px;
159
+ margin-bottom: 15px;
160
+ cursor: pointer;
161
+ border: 1px solid transparent;
162
+ transition: 0.2s;
163
+ }
164
+
165
+ .meal-card:hover { border-color: #00ff88; }
166
+ .meal-card img { width: 100%; height: 120px; object-fit: cover; border-radius: 4px; }
167
+ .meal-card-title { font-weight: bold; margin: 8px 0 4px 0; font-size: 1.1rem; }
168
+ .meal-card-macro { font-size: 0.85rem; color: #aaa; }
169
+
170
+ .add-meal-form {
171
+ background-color: #222;
172
+ padding: 15px;
173
+ border-radius: 8px;
174
+ margin-top: 20px;
175
+ border: 1px dashed #555;
176
+ }
177
+
178
+ .add-meal-form input[type="number"], .add-meal-form input[type="text"] {
179
+ width: 100%; padding: 8px; margin-bottom: 8px; background-color: #333;
180
+ border: 1px solid #444; color: white; border-radius: 4px; box-sizing: border-box;
181
+ }
182
+
183
+ .form-label { font-size: 0.85rem; color: #aaa; display: block; margin-bottom: 5px; }
184
+ .form-file { display: block; margin-bottom: 10px; font-size: 0.8rem; }
185
+ .form-row { display: flex; gap: 5px; }
186
+ .btn-save { margin-top: 10px; background-color: #444; color: white; }
test_images/bekon.jpg ADDED

Git LFS Details

  • SHA256: 7318691662af25add21aa28a89f71cd076c2e1ff89613632e1ca440ee236e07f
  • Pointer size: 130 Bytes
  • Size of remote file: 21.1 kB
test_images/burger_drwala.png ADDED

Git LFS Details

  • SHA256: a0eec117a9a9d8d7d81526d2ab83a77a8f6718727e556c23298ce5a20b325e3d
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
test_images/jajecznica_z_brokulem.jpg ADDED

Git LFS Details

  • SHA256: b4cf4880d82a77cd993a1f29047d06d29d3a93b135181e5b04f6b60c80f6894f
  • Pointer size: 130 Bytes
  • Size of remote file: 27.4 kB
test_images/kotelty_z_ziemniakami.jpeg ADDED

Git LFS Details

  • SHA256: f97015dc7d6a0b15fc6637359700f8ebb392c86a301c628bca9288413a18c071
  • Pointer size: 131 Bytes
  • Size of remote file: 209 kB
test_images/mini_pizza_salami.jpg ADDED

Git LFS Details

  • SHA256: 89cbe2f3315aab32e54d674c0279458c8971bcff355e9aeadb92f349145e9d56
  • Pointer size: 130 Bytes
  • Size of remote file: 22.8 kB
test_images/miska_salaty.jpg ADDED

Git LFS Details

  • SHA256: 4b6f2585836b954de347ba066f15054d194a14257dce15eac705ff4eaf755cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 31 kB
test_images/owsianka_z_owocami.jpg ADDED

Git LFS Details

  • SHA256: 5fe6ff462f5259c43151c9529895ca97627a7a44f85f05f3955a1347e11f1475
  • Pointer size: 130 Bytes
  • Size of remote file: 25 kB
test_images/pizza_slice.jpg ADDED

Git LFS Details

  • SHA256: 4ba29fd64c4b7e375a86aa9d7e7b553073c97bfc120b2a72af656cae687efa3f
  • Pointer size: 130 Bytes
  • Size of remote file: 88.8 kB
test_images/spaghetti-bolognese.jpg ADDED

Git LFS Details

  • SHA256: fceecfdc352113597fbd69707e7b313c31877b3110984f25d4b9c320d77aeedd
  • Pointer size: 130 Bytes
  • Size of remote file: 58.2 kB
test_images/stek_z_warzywami.jpg ADDED

Git LFS Details

  • SHA256: fabc5fe4306ac9eedfc99f7bca2c68322922db7ee651958f9e4a534769a90468
  • Pointer size: 130 Bytes
  • Size of remote file: 30.5 kB
test_images/super_bowl.png ADDED

Git LFS Details

  • SHA256: e4f23e357c395d48492629eb858fc98c2241579829191bb452c266b4ee11792e
  • Pointer size: 131 Bytes
  • Size of remote file: 157 kB